#!/usr/bin/perl
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
# $Header: //gpsql/feature/hd2.0/private-lili/src/include/catalog/calico.pl#2 $
#
# SLZY_HDR_END

use POSIX;
use Pod::Usage;
use Getopt::Long;
use Data::Dumper;
#use JSON;
use strict;
use warnings;

# SLZY_POD_HDR_BEGIN
# WARNING: DO NOT MODIFY THE FOLLOWING POD DOCUMENT:
# Generated by sleazy.pl version 9 (release Mon Feb  4 13:03:01 2013)
# Make any changes under SLZY_TOP_BEGIN/SLZY_LONG_BEGIN

=head1 NAME

B<calico.pl> - CaQL catalog query code generation

=head1 VERSION

 This document describes version 55 of calico.pl, released
 Mon Feb  4 13:07:23 2013.

=head1 SYNOPSIS

B<calico.pl> 

Options:

    -help           brief help message
    -man            full documentation
    -interactive    Run calico.pl as an interactive shell
    -metadata       json document describing the catalog
    -gperf          Construct an input file for gperf
    -dump           dump the data structures
    -logquery       turn on logging (elog) for all basic functions
    -logquery_hash  turn on filter logging
    -lockcheck      check locking
    -readlock       check locks for read
    -holdtablelock  hold table locks until transaction commit
    -lockblacklist  tables excluded from locking
    -lockwhitelist  tables covered by locking
    -filemap        build a json file mapping files to catalog tables
    -basedef        build a json file of basic caql definitions
    -uniqdef        build a json file of uniq caql definitions
    -inputfiles     list of cql input files

=head1 OPTIONS

=over 8

=item B<-help>

    Print a brief help message and exits.

=item B<-man>

    Prints the manual page and exits.

=item B<-interactive>

    Runs calico.pl as an interactive shell

=item B<-metadata> <filename> (Required)

    the metadata file is a json document describing the catalog generated by tidycat

=item B<-gperf> <filename>

    Construct an input file for gperf

=item B<-dump>

    dump the data structures

=item B<-logquery>

    Turn on logging (elog) for all basic functions.  Use caqltrack.sql to
process these statements from the logs to calculate code coverage.

=item B<-logquery_hash>

    turn on filter logging for all basic functions (filter duplicate callers)

=item B<-lockcheck>

    acquire locks to prevent write skew

=item B<-readlock>

    acquire primary key locks in basic fn prior to read

=item B<-holdtablelock>

    Normally, table locks are released at heap_close at caql_endscan.
When this flag is set, the table lock is retained until transaction
commit/abort.

=item B<-lockblacklist>

    tables excluded from locking

=item B<-lockwhitelist>

    tables covered by locking

=item B<-filemap> <filename>

    build a json file mapping source files to catalog tables referenced in cql statements (for pablopcatso)

=item B<-basedef> <filename>

    build a json file of basic caql definitions

=item B<-uniqdef> <filename>

    build a json file of uniq caql definitions

=item B<-inputfiles> <filename>

    file containing list of cql input files (1 per line).  If defined, ignores the filenames from the argument list and only processes files listed in the input file.


=back

=head1 DESCRIPTION

calico.pl discovers cql() functions with CaQL statements and generates
the corresponding C functions in catquery.c.  It acts as a compiler,
parsing CaQL, a small, SQL-like query language, and it generates the
corresponding low-level heap/index lookup functions, or CaQL "basic
queries".  At runtime, the cql functions use a hash of the CaQL
statement to dispatch to the associated "basic query" function.  

The CaQL parsing and code generation is driven by the tidycat json
metadata file, which describes the catalog tables, their indexes and
dependencies.

=head2 INSERT/UPDATE Syntax

CaQL has been extended to handle INSERT and UPDATE.

=head2 Locking

The readlock/lockcheck options use the tidycat dependency information
(JSON) to generate the insert/update/delete ("iud") locking functions.
Without locking, concurrent transactions can cause catalog corruption,
e.g the case where one transaction does CREATE TABLE in a schema and a
concurrent transaction DROPs the schema.  The caql_lockwell() function
locks the "primary key" index(es) and any associated foreign key
indexes, ensuring that referential integrity is maintained.

=head1 CAVEATS/FUTURE WORK

=head2 External Catalog

After the entire catalog is converted to CaQL, we should be able to
restructure the system to support a single, external catalog.  At this
stage we may switch from calico code generation to a direct
implementation of catquery as an external catalog API.

=head2 Catalog Bloat

Need to restructure the catalog to eliminate bloat.



=head1 AUTHORS

Apache HAWQ

Address bug reports and comments to: dev@hawq.apache.org

=cut
# SLZY_POD_HDR_END

# SLZY_GLOB_BEGIN
my $glob_id = "";
my $glob_glob;
# SLZY_GLOB_END

sub glob_validate
{
	# map files to catalog tables
	$glob_glob->{fil2tab} = {};

	# look for JSON on normal path, but if it isn't there, add the
	# directory containing this script and look for it there...
	unless (eval "require JSON")
	{
		use FindBin qw($Bin);
		use lib "$Bin";

		unless (eval "require JSON")
		{
			die("Fatal Error: The required package JSON is not installed -- please download it from www.cpan.org\n");
			exit(1);
		}
	}

	# readlock implies general lockcheck
	$glob_glob->{lockcheck} = 1	
		if (exists($glob_glob->{readlock})
			&& ($glob_glob->{readlock}));

	# DO NOT extend this list!  These tables are *exempt* from the
	# autogenerated primary locking mechanism for historical reasons.
	#
	my @pklock_exception_tables = 
		qw(
pg_class
pg_authid
pg_largeobject
pg_statistic
pg_stat_last_operation
pg_stat_last_shoperation
gp_distribution_policy
pg_depend
pg_shdepend
pg_description
pg_shdescription
);

	# build locking whitelist and blacklist

	my $blacklist = {};
	for my $tname (@pklock_exception_tables)
	{
		$blacklist->{$tname} = 1;
	}
	if (exists($glob_glob->{lockblacklist})
		&& defined($glob_glob->{lockblacklist}))
	{
		my @foo = split(/,/, $glob_glob->{lockblacklist});

		for my $tname (@foo)
		{
			$blacklist->{$tname} = 1;
		}
	}

	$glob_glob->{lock_exceptions} = {blacklist => $blacklist};

	if (exists($glob_glob->{lockwhitelist})
		&& defined($glob_glob->{lockwhitelist}))
	{
		my @foo = split(/,/, $glob_glob->{lockwhitelist});
		my $whitelist = {};

		for my $tname (@foo)
		{
			die "table $tname cannot be on both " .
				"lockwhitelist and lockblacklist"
				if (exists($blacklist->{$tname}));

			$whitelist->{$tname} = 1;
		}

		$glob_glob->{lock_exceptions}->{whitelist} = $whitelist
			if (scalar(@foo));
	}

#	print Data::Dumper->Dump([\$glob_glob]);
}

# SLZY_CMDLINE_BEGIN
# WARNING: DO NOT MODIFY THE FOLLOWING SECTION:
# Generated by sleazy.pl version 9 (release Mon Feb  4 13:03:01 2013)
# Make any changes under SLZY_TOP_BEGIN/SLZY_LONG_BEGIN
# Any additional validation logic belongs in glob_validate()

BEGIN {
	    my $s_help              = 0;     # brief help message
	    my $s_man               = 0;     # full documentation
	    my $s_interactive       = 0;     # Run calico.pl as an interactive shell
	    my $s_metadata;                  # json document describing the catalog
	    my $s_gperf;                     # Construct an input file for gperf
	    my $s_dump              = 0;     # dump the data structures
	    my $s_logquery          = 0;     # turn on logging (elog) for all basic functions
	    my $s_logquery_hash     = 0;     # turn on filter logging
	    my $s_lockcheck         = 0;     # check locking
	    my $s_readlock          = 0;     # check locks for read
	    my $s_holdtablelock     = 0;     # hold table locks until transaction commit
	    my $s_lockblacklist;             # tables excluded from locking
	    my $s_lockwhitelist;             # tables covered by locking
	    my $s_filemap;                   # build a json file mapping files to catalog tables
	    my $s_basedef;                   # build a json file of basic caql definitions
	    my $s_uniqdef;                   # build a json file of uniq caql definitions
	    my $s_inputfiles;                # list of cql input files

	my $slzy_argv_str;
	$slzy_argv_str = quotemeta(join(" ", @ARGV))
		if (scalar(@ARGV));

    GetOptions(
		'help|?'                     =>     \$s_help,
		'man'                        =>     \$s_man,
		'interactive'                =>     \$s_interactive,
		'metadata|json=s'            =>     \$s_metadata,
		'gperf|perf:s'               =>     \$s_gperf,
		'dump'                       =>     \$s_dump,
		'logquery|elog'              =>     \$s_logquery,
		'logquery_hash'              =>     \$s_logquery_hash,
		'lockcheck'                  =>     \$s_lockcheck,
		'readlock'                   =>     \$s_readlock,
		'holdtablelock|holdlock'     =>     \$s_holdtablelock,
		'lockblacklist|lbl:s'        =>     \$s_lockblacklist,
		'lockwhitelist|lwl:s'        =>     \$s_lockwhitelist,
		'filemap:s'                  =>     \$s_filemap,
		'basedef:s'                  =>     \$s_basedef,
		'uniqdef:s'                  =>     \$s_uniqdef,
		'inputfiles|infiles:s'       =>     \$s_inputfiles,
               )
        or pod2usage(2);

	pod2usage(-msg => $glob_id, -exitstatus => 1) if $s_help;
	pod2usage(-msg => $glob_id, -exitstatus => 0, -verbose => 2) if $s_man;
	
	
	$glob_glob = {};
	
	
	# version and properties from json definition
	$glob_glob->{_sleazy_properties} = {};
	$glob_glob->{_sleazy_properties}->{version} = '55';
	$glob_glob->{_sleazy_properties}->{AUTHORNAME} = 'Jeffrey I Cohen';
	$glob_glob->{_sleazy_properties}->{BUGEMAIL} = 'Address bug reports and comments to: jcohen@greenplum.com';
	$glob_glob->{_sleazy_properties}->{COPYDATES} = '2011, 2012, 2013';
	$glob_glob->{_sleazy_properties}->{COPYHOLDER} = 'Greenplum';
	$glob_glob->{_sleazy_properties}->{slzy_date} = '1360012043';
	$glob_glob->{_sleazy_properties}->{slzy_argv_str} = $slzy_argv_str;
	
	    die ("missing required argument for 'metadata'")
	    unless (defined($s_metadata));
	    if (defined($s_metadata))
	    {
	    die ("invalid argument for 'metadata': file $s_metadata does not exist")
	    unless (-e $s_metadata);
	    }
	    if (defined($s_inputfiles))
	    {
	    die ("invalid argument for 'inputfiles': file $s_inputfiles does not exist")
	    unless (-e $s_inputfiles);
	    }
	
	$glob_glob->{interactive}    =  $s_interactive      if (defined($s_interactive));
	$glob_glob->{metadata}       =  $s_metadata         if (defined($s_metadata));
	$glob_glob->{gperf}          =  $s_gperf            if (defined($s_gperf));
	$glob_glob->{dump}           =  $s_dump             if (defined($s_dump));
	$glob_glob->{logquery}       =  $s_logquery         if (defined($s_logquery));
	$glob_glob->{logquery_hash}  =  $s_logquery_hash    if (defined($s_logquery_hash));
	$glob_glob->{lockcheck}      =  $s_lockcheck        if (defined($s_lockcheck));
	$glob_glob->{readlock}       =  $s_readlock         if (defined($s_readlock));
	$glob_glob->{holdtablelock}  =  $s_holdtablelock    if (defined($s_holdtablelock));
	$glob_glob->{lockblacklist}  =  $s_lockblacklist    if (defined($s_lockblacklist));
	$glob_glob->{lockwhitelist}  =  $s_lockwhitelist    if (defined($s_lockwhitelist));
	$glob_glob->{filemap}        =  $s_filemap          if (defined($s_filemap));
	$glob_glob->{basedef}        =  $s_basedef          if (defined($s_basedef));
	$glob_glob->{uniqdef}        =  $s_uniqdef          if (defined($s_uniqdef));
	$glob_glob->{inputfiles}     =  $s_inputfiles       if (defined($s_inputfiles));
	
	glob_validate();


}
# SLZY_CMDLINE_END

# SLZY_TOP_BEGIN
if (0)
{
    my $bigstr = <<'EOF_bigstr';
{
   "args" : [
      {
         "alias" : "?",
         "long" : "Print a brief help message and exits.",
         "name" : "help",
         "required" : "0",
         "short" : "brief help message",
         "type" : "untyped"
      },
      {
         "long" : "Prints the manual page and exits.",
         "name" : "man",
         "required" : "0",
         "short" : "full documentation",
         "type" : "untyped"
      },
      {
         "long" : "Runs calico.pl as an interactive shell",
         "name" : "interactive",
         "required" : "0",
         "short" : "Run calico.pl as an interactive shell",
         "type" : "untyped"
      },
      {
         "alias" : "json",
         "long" : "the metadata file is a json document describing the catalog generated by tidycat",
         "name" : "metadata",
         "required" : "1",
         "short" : "json document describing the catalog",
         "type" : "file"
      },
      {
         "alias" : "perf",
         "long" : "Construct an input file for gperf",
         "name" : "gperf",
         "required" : "0",
         "short" : "Construct an input file for gperf",
         "type" : "outfile"
      },
      {
         "long" : "dump the data structures",
         "name" : "dump",
         "required" : "0",
         "short" : "dump the data structures",
         "type" : "untyped"
      },
      {
         "alias" : "elog",
         "long" : "$loglong",
         "name" : "logquery",
         "required" : "0",
         "short" : "turn on logging (elog) for all basic functions",
         "type" : "untyped"
      },
      {
         "long" : "turn on filter logging for all basic functions (filter duplicate callers)",
         "name" : "logquery_hash",
         "required" : "0",
         "short" : "turn on filter logging",
         "type" : "untyped"
      },
      {
         "long" : "acquire locks to prevent write skew",
         "name" : "lockcheck",
         "required" : "0",
         "short" : "check locking",
         "type" : "untyped"
      },
      {
         "long" : "acquire primary key locks in basic fn prior to read",
         "name" : "readlock",
         "required" : "0",
         "short" : "check locks for read",
         "type" : "untyped"
      },
      {
         "alias" : "holdlock",
         "long" : "$holdlocklong",
         "name" : "holdtablelock",
         "required" : "0",
         "short" : "hold table locks until transaction commit",
         "type" : "untyped"
      },
      {
         "alias" : "lbl",
         "name" : "lockblacklist",
         "required" : "0",
         "short" : "tables excluded from locking",
         "type" : "string"
      },
      {
         "alias" : "lwl",
         "name" : "lockwhitelist",
         "required" : "0",
         "short" : "tables covered by locking",
         "type" : "string"
      },
      {
         "long" : "build a json file mapping source files to catalog tables referenced in cql statements (for pablopcatso)",
         "name" : "filemap",
         "required" : "0",
         "short" : "build a json file mapping files to catalog tables",
         "type" : "outfile"
      },
      {
         "name" : "basedef",
         "required" : "0",
         "short" : "build a json file of basic caql definitions",
         "type" : "outfile"
      },
      {
         "name" : "uniqdef",
         "required" : "0",
         "short" : "build a json file of uniq caql definitions",
         "type" : "outfile"
      },
      {
         "alias" : "infiles",
         "long" : "file containing list of cql input files (1 per line).  If defined, ignores the filenames from the argument list and only processes files listed in the input file.",
         "name" : "inputfiles",
         "required" : "0",
         "short" : "list of cql input files",
         "type" : "file"
      }
   ],
   "long" : "$toplong",
   "properties" : {
      "AUTHORNAME" : "Jeffrey I Cohen",
      "BUGEMAIL" : "Address bug reports and comments to: jcohen@greenplum.com",
      "COPYDATES" : "2011, 2012, 2013",
      "COPYHOLDER" : "Greenplum",
      "slzy_date" : 1360012043
   },
   "short" : "CaQL catalog query code generation",
   "version" : "55"
}

EOF_bigstr
}
# SLZY_TOP_END

# SLZY_LONG_BEGIN
if (0)
{
	my $toplong = <<'EOF_toplong';
calico.pl discovers cql() functions with CaQL statements and generates
the corresponding C functions in catquery.c.  It acts as a compiler,
parsing CaQL, a small, SQL-like query language, and it generates the
corresponding low-level heap/index lookup functions, or CaQL "basic
queries".  At runtime, the cql functions use a hash of the CaQL
statement to dispatch to the associated "basic query" function.  

The CaQL parsing and code generation is driven by the tidycat json
metadata file, which describes the catalog tables, their indexes and
dependencies.

{HEAD2} INSERT/UPDATE Syntax

CaQL has been extended to handle INSERT and UPDATE.

{HEAD2} Locking

The readlock/lockcheck options use the tidycat dependency information
(JSON) to generate the insert/update/delete ("iud") locking functions.
Without locking, concurrent transactions can cause catalog corruption,
e.g the case where one transaction does CREATE TABLE in a schema and a
concurrent transaction DROPs the schema.  The caql_lockwell() function
locks the "primary key" index(es) and any associated foreign key
indexes, ensuring that referential integrity is maintained.

{HEAD1} CAVEATS/FUTURE WORK

{HEAD2} External Catalog

After the entire catalog is converted to CaQL, we should be able to
restructure the system to support a single, external catalog.  At this
stage we may switch from calico code generation to a direct
implementation of catquery as an external catalog API.

{HEAD2} Catalog Bloat

Need to restructure the catalog to eliminate bloat.


EOF_toplong

	my $loglong = <<'EOF_loglong';
Turn on logging (elog) for all basic functions.  Use caqltrack.sql to
process these statements from the logs to calculate code coverage.
EOF_loglong

	my $holdlocklong = <<'EOF_holdlocklong';
Normally, table locks are released at heap_close at caql_endscan.
When this flag is set, the table lock is retained until transaction
commit/abort.
EOF_holdlocklong
}
# SLZY_LONG_END


sub doformat
{
	my ($bigstr, $kv) = @_;

	my %blankprefix;

	# find format expressions with leading blanks
	if ($bigstr =~ m/\n/)
	{
		my @foo = split(/\n/, $bigstr);

		for my $lin (@foo)
		{
			next unless ($lin =~ m/^\s+\{.*\}/);

			# find the first format expression after the blank prefix
			my @baz = split(/\}/, $lin, 2);

			my $firstf = shift @baz;

			my @zzz = ($firstf =~ m/^(\s+)\{(.*)$/);

			next unless (defined($zzz[1]) &&
						 length($zzz[1]));

			my $k2 = quotemeta($zzz[1]);

			if (exists($blankprefix{$k2}))
			{
#				print STDERR Data::Dumper->Dump(\@foo);
#				print STDERR Data::Dumper->Dump([\%blankprefix]);
				die "duplicate use of prefixed pattern $k2 for\n$bigstr"
					unless ($blankprefix{$k2} eq $zzz[0]);
			}


			# store the prefix
			$blankprefix{$k2} = $zzz[0];
		}

	}

#	print Data::Dumper->Dump([%blankprefix]);

	while (my ($kk, $vv) = each(%{$kv}))
	{
		my $subi = '{' . quotemeta($kk) . '}';
		my $v2 = $vv;

		if (exists($blankprefix{quotemeta($kk)}) && 
			($v2 =~ m/\n/))
		{
			my @foo = split(/\n/, $v2);

			# for a multiline substitution, prefix every line with the
			# offset of the original token
			$v2 = join("\n" . $blankprefix{quotemeta($kk)}, @foo);

			# fixup trailing newline if necessary
			if ($vv =~ m/\n$/)
			{
				$v2 .= "\n"
					unless ($v2 =~ m/\n$/);
			}

		}

		$bigstr =~ s/$subi/$v2/gm;
	}

	return $bigstr;
}

# only allow alphanums, and quote all other chars as hex string
sub sql_func_quurl
{
    my $str = shift;

    $str =~ s/([^a-zA-Z0-9])/uc(sprintf("%%%02lx",  ord $1))/eg;
    return $str;
}

# more "relaxed" version of quurl function -- allow basic punctuation
# with the exception of "%" and quote characters
sub sql_func_quurl2
{
    my $str = shift;

    my $pat1 = '[^a-zA-Z0-9' .
        quotemeta(' ~!@#$^&*()-_=+{}|[]:;<>,.?/') . ']';
    $str =~ s/($pat1)/uc(sprintf("%%%02lx",  ord $1))/eg;
    return $str;
}

# unconvert quoted strings 
sub sql_func_unquurl
{
    my $str = shift;

    $str =~ s/\%([A-Fa-f0-9]{2})/pack('C', hex($1))/seg;
    return $str;
}

sub scanhdr
{
    my $bigstr = <<'EOF_bigstr';
SysScanDesc  {SCAN};
{RELATIONDEF}
{HEAPTUPLEDEF}
EOF_bigstr

	return $bigstr;
} # end scanhdr

sub scankeyinit
{
    my $bigstr = <<'EOF_bigstr';
ScanKeyInit(&{KEY}[{KEYOFFSET}],
			{ANUMKEYCOL},
			BTEqualStrategyNumber, {FMGREQOID},
			{KVTRANSFORM}({KEYVAL}));
EOF_bigstr

	return $bigstr;
} # end scankeyinit

sub readpkeyhashinit
{
    my $bigstr = <<'EOF_bigstr';
newhash = caql_pkhash(pCtx, newhash, ({KEYVAL}), 
					  false /* isnull */, {MHTYPOID});

EOF_bigstr

	return $bigstr;
} # end readkpeyhashinit

sub idxokfunc
{
    my $bigstr = <<'EOF_bigstr';
/* always use the index (if possible) unless the caller states otherwise */
if (!pCtx->cq_setidxOK)
	pCtx->cq_useidxOK = true;

EOF_bigstr

	return $bigstr;
}


sub nosyscacheid
{
    my $bigstr = <<'EOF_bigstr';
Assert (!pCtx->cq_usesyscache);
pCtx->cq_usesyscache = false; /* complain in debug, work in production */

EOF_bigstr

	return $bigstr;
}

sub syscacheid
{
    my $bigstr = <<'EOF_bigstr';
/* always use the syscache unless the caller states otherwise */
if (!pCtx->cq_setsyscache)
{
	pCtx->cq_usesyscache = true;

	/* Normally, must match all columns of the index to use syscache,
	 * except for case of SearchSysCacheList 
	 */
	if (!pCtx->cq_bCacheList && (pCtx->cq_NumKeys != {NUMSYSCACHEIDXCOLS}))
		pCtx->cq_usesyscache = false;

	/* ARD-28, MPP-16119: can only use SnapshowNow with syscache */
	if (pCtx->cq_snapshot != SnapshotNow)
	{
		/* Complain in debug, but behave in production */
		Assert(!pCtx->cq_usesyscache);

		pCtx->cq_setsyscache = true;
		pCtx->cq_usesyscache = false;
	}
}

if (pCtx->cq_usesyscache)
{
	pCtx->cq_cacheId      = {SYSCACHEIDENTIFIER};

	/* number of keys must match (unless a SearchSysCacheList ) */
	Assert(pCtx->cq_bCacheList || (pCtx->cq_NumKeys == {NUMSYSCACHEIDXCOLS}));
}

/* else perform heap/index scan */
EOF_bigstr

	return $bigstr;
} # end syscacheid

sub syscachetupdesc
{
    my $bigstr = <<'EOF_bigstr';
/* XXX XXX : NOTE: disable heap_open/lock for syscache 
	only if : no external lock mode or external relation
*/
if ((!pCtx->cq_setlockmode) &&
	pCtx->cq_usesyscache &&
	(AccessShareLock == pCtx->cq_lockmode))
{
	pCtx->cq_externrel = true; /* pretend we have external relation */
	pCtx->cq_heap_rel  = InvalidRelation;
	/* 
		pCtx->cq_tupdesc  = SysCache[pCtx->cq_cacheId]->cc_tupdesc;
	*/

	return NULL; /* XXX XXX: return early - don't open heap */
}
else
EOF_bigstr

	return $bigstr;
} # end syscachetupdesc

## ARD-27: check lock strength for query/update in caql basic functions
sub rel_lock_test
{
    my $bigstr = <<'EOF_bigstr';
/* test lock strength */

if ((!pCtx->cq_setlockmode) &&
	RelationIsValid(pCtx->cq_heap_rel))
{
	LOCKMODE mode = pCtx->cq_lockmode;
	LOCKMASK gm   = 
		LockRelationOid_GetGrantMask(
			RelationGetRelid(pCtx->cq_heap_rel),
			"{FUNCNAME}");

	for (; mode < MAX_LOCKMODES; mode++)
	{
		if (gm & LOCKBIT_ON(mode))
			break;
	}
	Assert(gm & LOCKBIT_ON(mode));
}
EOF_bigstr

	return $bigstr;
} # end rel_lock_test


sub beginscan
{
    my $bigstr = <<'EOF_bigstr';
systable_beginscan({REL},
				   {INDEX},
				   {INDEXOK},
				   {SNAPSHOT}, {KEYNUM}, {KEY});
EOF_bigstr

	return $bigstr;
} # end beginscan

sub nextscan
{
    my $bigstr = "systable_getnext({SCAN})";

	return $bigstr;
} # end nextscan
sub endscan
{
    my $bigstr = "systable_endscan({SCAN})";

	return $bigstr;
} # end endscan

sub scanfuncbody
{
    my $bigstr = <<'EOF_bigstr';
void
{FUNCNAME}({FUNCARGS})
{
	{SCANHDR}	

	{OPENREL}

	{SCANKEYINIT}
	{SCAN} = {BEGINSCAN}

	{TUPLELOOPGUTS}

	/* Clean up after the scan */
	{ENDSCAN}
	{CLOSEREL}
}
EOF_bigstr

	return $bigstr;
} # end scanfuncbody

# XXX XXX:
# NOTE: logging function requires cql filename, lineno for debug
#

sub selectfrom_elog_hash
{
    my $bigstr = <<'EOF_bigstr';
SUPPRESS_ERRCONTEXT_DECLARE;
SUPPRESS_ERRCONTEXT_PUSH();
if (gp_enable_caql_logging) 
{
	CaQLLogTag caqllogtag;
	CaQLLogEntry *entry;
	int hashcode;
	int len;
	bool found = false;

	/* memset caqllogtag is needed, as memcmp is done during hash_search_with_hash_value */
	MemSet(&caqllogtag, 0, sizeof(caqllogtag));
	len = (strlen(pcql->filename) > MAXPGPATH ? MAXPGPATH : strlen(pcql->filename));
	memcpy(caqllogtag.filename, pcql->filename, len);
	caqllogtag.lineno = pcql->lineno;

	/* compute the hash */
	hashcode = caqlLogHashCode(&caqllogtag);

	/* look up the hash table to see if this line has been logged before */
	entry = (CaQLLogEntry *) hash_search_with_hash_value(CaQLLogHash,
								(void *)&caqllogtag,
								hashcode,
								HASH_ENTER, &found);
		
	if (!found)
		elog(LOG, "catquery: %s caller: %s %d %d %d ",
	 		"{FUNCNAME}", pcql->filename, pcql->lineno, pCtx->cq_uniqquery_code,
	 		DatumGetObjectId(pcql->cqlKeys[0])
		);
}
SUPPRESS_ERRCONTEXT_POP();
EOF_bigstr
	return $bigstr;
}


# NOTE WELL: always pass the first argument (pcql->cqlKeys[0] as an OID,
# even if it isn't one (or doesn't exist!).  We will sort it out
# later during log processing
sub selectfrom_elog
{
    my $bigstr = <<'EOF_bigstr';
caql_logquery("{FUNCNAME}", pcql->filename, pcql->lineno, pCtx->cq_uniqquery_code,
			  DatumGetObjectId(pcql->cqlKeys[0]));
EOF_bigstr
	return $bigstr;
}

# lock entire table if cannot use primary keys 
# (used for both read and insert case)
sub readinsert_entiretable_exclusive
{
    my $bigstr = <<'EOF_bigstr';
/* cannot get primary key lock -- {READPK_FAILREASON} */
if (pCtx->cq_setpklock)
{
	LOCKMODE	pklockmode = AccessExclusiveLock;
	bool		dontWait   = false;

	if (!pCtx->cq_pklock_excl)
		pklockmode = AccessShareLock;

	caql_lock_entiretable(pCtx, {RELATIONID},
						  pklockmode, dontWait);
}
EOF_bigstr
	return $bigstr;
}

# share lock entire table (if necessary) if *can* use primary keys 
# (used for both read and insert case)
sub readinsert_entiretable_share
{
    my $bigstr = <<'EOF_bigstr';
/*
 if any caql statement on {RELATIONID} cannot use a
 primary key, then need to obtain a share lock
 (to block the table exclusive lock)
*/
if (bLockEntireTable)
	caql_lock_entiretable(pCtx, {RELATIONID},
						  AccessShareLock, false);
EOF_bigstr
	return $bigstr;
}

sub get_readpk
{
    my $bigstr = <<'EOF_bigstr';
/* can get primary key lock for read: {LW_TNAME}({COLSTR}) */
if (pCtx->cq_setpklock)
{
	LOCKMODE	pklockmode = AccessExclusiveLock;
	bool		dontWait   = false;
	Oid			newhash	   = 0;

	if (!pCtx->cq_pklock_excl)
		pklockmode = AccessShareLock;

	{READPK_ENTIRETABLE}
	{READPK_INIT}
	caql_lockwell(pCtx, {RELATIONID},
				  pklockmode, NULL,
				  "{LW_TNAME}",
				  "{COLSTR}",
				  {INDEX},
				  newhash,
				  dontWait,
				  true /* ignore invalid tuple */
		);

	/* XXX XXX: disable syscache because it might need to be invalidated!! */
//	pCtx->cq_setsyscache = true;
//	pCtx->cq_usesyscache = false;
	AcceptInvalidationMessages();  /* syscache could be out of date after lock wait */
}
EOF_bigstr
	return $bigstr;
}

sub selectfrom
{
my $selguts  = <<'EOF_bigstr';
static
SysScanDesc
{FUNCNAME}({FUNCARGS})
{
	{SCANHDR}	
	Relation	rel;

	{SELECTFROM_ELOG}
	{GET_READPK}
	{SYSCACHECHECK}
	pCtx->cq_relationId = {RELATIONID};

	if (!pCtx->cq_externrel)
	{
		{SYSCACHETUPDESC}
		{
			pCtx->cq_heap_rel = heap_open(pCtx->cq_relationId, 
										  pCtx->cq_lockmode);
			pCtx->cq_tupdesc  = RelationGetDescr(pCtx->cq_heap_rel);
		}
	}
	else
	{
		/* make sure the supplied relation matches the caql */
		if (RelationIsValid(pCtx->cq_heap_rel))
		{
			Assert({RELATIONID} == 
				   RelationGetRelid(pCtx->cq_heap_rel));
			pCtx->cq_tupdesc  = RelationGetDescr(pCtx->cq_heap_rel);
		}
		{RELLOCKTEST}
	}		

	rel = pCtx->cq_heap_rel;

	if (pCtx->cq_usesyscache) return NULL; /* XXX XXX: don't init scan */

	{SCANKEYINIT}
	{SCAN} = {BEGINSCAN}

	return ({SCAN});
}
EOF_bigstr

	return $selguts;
} # end selectfrom

sub deletefrom
{
my $loopguts  = <<'EOF_bigstr';
/* Delete all the matching tuples */
while (HeapTupleIsValid({TUPLE} = {NEXTSCAN}))
{
	if (HeapTupleIsValid({TUPLE}))
		simple_heap_delete({REL}, &{TUPLE}->t_self);
}
EOF_bigstr

	return doformat(scanfuncbody(),
					{
						TUPLELOOPGUTS => $loopguts
					});
} # end deletefrom

sub duplicate_obj
{
my $loopguts  = <<'EOF_bigstr';
{TUPLE} = {NEXTSCAN};
if (HeapTupleIsValid({TUPLE}))
	ereport(ERROR,
			(errcode(ERRCODE_DUPLICATE_OBJECT),
			 errmsg({DUPOBJEXISTSMSG})
			 errOmitLocation(true)));
EOF_bigstr

	return doformat(scanfuncbody(),
					{
						TUPLELOOPGUTS => $loopguts
					});
} # end duplicate_obj

sub undef_obj
{
my $loopguts  = <<'EOF_bigstr';
{TUPLE} = {NEXTSCAN};
if (!HeapTupleIsValid({TUPLE}))
	ereport(ERROR,
			(errcode(ERRCODE_UNDEFINED_OBJECT),
			 errmsg({UNDEFOBJNOTEXISTMSG})
			 errOmitLocation(true)));
EOF_bigstr

	return doformat(scanfuncbody(),
					{
						TUPLELOOPGUTS => $loopguts
					});
} # end undef_obj

# construct a basic function for an INSERT
# 
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
#
# Note that if locking is enabled (as a calico option), and
# bLockEntireTable is set, then always lock the table Share (really
# Intent Exclusive) if the table has a primary key, or Exclusive if
# the table does *not* have a primary key.  Unlike basic functions
# which fetch tuples (SELECT/DELETE), the INSERT *always* gets a lock
# with the IUD functions, so the basic function for INSERT does *not*
# use cq_setpklock to control lock acquistion.
#
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX


sub insertinto
{
my $insertguts  = <<'EOF_bigstr';
static
SysScanDesc
{FUNCNAME}({FUNCARGS})
{
	Relation	rel;

	{SELECTFROM_ELOG}

	pCtx->cq_relationId = {RELATIONID};

	{INSERTPK_ENTIRETABLE}
	if (!pCtx->cq_externrel)
	{
		{SYSCACHETUPDESC}
		{
			pCtx->cq_heap_rel = heap_open(pCtx->cq_relationId, 
										  pCtx->cq_lockmode);
			pCtx->cq_tupdesc  = RelationGetDescr(pCtx->cq_heap_rel);
		}
	}
	else
	{
		/* make sure the supplied relation matches the caql */
		if (RelationIsValid(pCtx->cq_heap_rel))
		{
			Assert({RELATIONID} == 
				   RelationGetRelid(pCtx->cq_heap_rel));
			pCtx->cq_tupdesc  = RelationGetDescr(pCtx->cq_heap_rel);
		}
		{RELLOCKTEST}
	}		

	rel = pCtx->cq_heap_rel;

	return NULL; /* XXX XXX: don't init scan */
}
EOF_bigstr

	return $insertguts;
} # end insertinto

sub unique_index_by_cols
{
	my ($bigh, $tname, $colstr) = @_;

	return undef
		unless (exists($bigh->{$tname}->{indexes}));

	my @ikeylist;

	my $idxl = $colstr;

	$idxl =~ s/^\s+//;
	$idxl =~ s/\s+$//;

	if ($idxl =~ m/\,/)
	{
		@ikeylist = split(/\s*\,\s*/, $idxl)
	}
	else
	{
		# single key
		push @ikeylist, $idxl;
	}

#	print "$tname, $colstr\n";

	my $cola;

	for my $idx (@{$bigh->{$tname}->{indexes}})
	{
#		print Data::Dumper->Dump(\@ikeylist);
#		print Data::Dumper->Dump([$idx]);

		next
			unless (exists($idx->{unique}) &&
					$idx->{unique});

		next unless (scalar(@{$idx->{cols}}) == scalar(@ikeylist));

#		print Data::Dumper->Dump([$idx]);

		my $bMatch;

		$cola = [];

		$bMatch = 1;

		# match keys
		for my $ii (0..(scalar(@ikeylist)-1))
		{
			# list of [colname, col_ops]
			#
			# so match colname in position 0

			my $col = $idx->{cols}->[$ii];

			unless ($col->[0] eq $ikeylist[$ii])
			{
				$bMatch = 0;
				last;
			}

			push @{$cola}, { name => $col->[0], ops => $col->[1] };
		}
		return [$idx->{CamelCaseIndexId}, $cola]
			if ($bMatch);
	}

	return undef;
	
}

# choose an index which can satisfy the predicate.  
# If bexact=true, then index cols must match predicate col order 
# (for ORDER BY)
sub choose_index
{
	my ($bigh, $tname, $wpred, $bexact) = @_;

	return undef
		unless (exists($bigh->{$tname}->{indexes}));

	my $numkeys = scalar(@{$wpred});

	my %pkeyh; # hash of key col names
	for my $pred (@{$wpred})
	{
		$pkeyh{$pred->{key}} = 1;
	}

	# for all indexes with matching key columns, build a hash of lists
	# of matching indexes (hash by total keys in index).  We want the
	# "best fit", ie the index has the same number of key columns as
	# the predicate.
	my %indbynumkey;

	for my $inds (@{$bigh->{$tname}->{indexes}})
	{
		my $matchcol;
		my $icolnum;

		$matchcol = 0;
		$icolnum  = scalar(@{$inds->{cols}});

		# don't check indexes with insufficient key columns
		next
			if ($icolnum < $numkeys);

#		for my $icol (@{$inds->{cols}})
		for my $jj (0..(scalar(@{$inds->{cols}})-1))
		{
			my $icol = $inds->{cols}->[$jj];

			# index prefix must match keys.  If it does, increment the
			# match count.  If all keys are matched then this index is
			# a candidate, even if it has extra trailing columns.

			if ($bexact)
			{
				# for an *exact* match, key col order must match predicate
				last unless ($icol->[0] eq $wpred->[$jj]->{key});
			}

			last unless (exists($pkeyh{$icol->[0]}));
			$matchcol++;
		}
		
		# skip unless found all columns in index
		next
			unless ($matchcol == $numkeys);
		
		$indbynumkey{$icolnum} = []
			unless (exists($indbynumkey{$icolnum}));

		my $iih = 
		{CamelCaseIndexId => $inds->{CamelCaseIndexId}};

		$iih->{unique} = 1
			if (exists($inds->{unique}) && $inds->{unique});

		# if this table is a "dependent class" of the parent table,
		# then we can lock the primary key of the parent if we aren't
		# using all the columns of our index.
		$iih->{primarykey_prefix} = $inds->{primarykey_prefix}
			if (exists($inds->{primarykey_prefix}));

		# predicate matches all columns of index 
		# (necessary for syscache or pkey locking)
		$iih->{allcols} = 1
			if ($icolnum == $numkeys);

		$iih->{numcols} = $icolnum;

		# can use the syscache iff match all cols of index
		if (exists($inds->{with}->{syscacheid}) &&
			($icolnum == $numkeys))
		{
			$iih->{SysCacheIdentifier} = $inds->{with}->{syscacheid};
		}

		push @{$indbynumkey{$icolnum}}, $iih;
	}

	return undef
		unless (scalar(keys(%indbynumkey)));

	# walk the set of matching indexes.  Best case is exact prefix
	# match, ie the index columns are the same as the predicate key
	# cols.  But we will accept any case with key prefix.  First match
	# wins.  Assume max 10 keys in index.
	for my $ii ($numkeys..10)
	{
		next unless (exists($indbynumkey{$ii}));
		
		# first one is good enough
		return $indbynumkey{$ii}->[0];
	}

	return undef;
} # end choose_index

sub check_oby_index
{
	my ($bigh, $tname, $iname, $wpred) = @_;

	die "bad oby index: $iname - no indexes on $tname"
		unless (exists($bigh->{$tname}->{indexes}));

	my $numkeys = scalar(@{$wpred});

	my $iih;

	for my $inds (@{$bigh->{$tname}->{indexes}})
	{
		next unless ($iname eq $inds->{CamelCaseIndexId});

		my $icolnum  = scalar(@{$inds->{cols}});

		# NOTE: same logic as choose_index()
		$iih = 
		{CamelCaseIndexId => $inds->{CamelCaseIndexId}};

		$iih->{unique} = 1
			if (exists($inds->{unique}) && $inds->{unique});

		# if this table is a "dependent class" of the parent table,
		# then we can lock the primary key of the parent if we aren't
		# using all the columns of our index.
		$iih->{primarykey_prefix} = $inds->{primarykey_prefix}
			if (exists($inds->{primarykey_prefix}));

		# predicate matches all columns of index 
		# (necessary for syscache or pkey locking)
		$iih->{allcols} = 1
			if ($icolnum == $numkeys);

		$iih->{numcols} = $icolnum;

		# XXX XXX: for ORDER BY case, we might use the syscache even
		# if all the index columns *don't* match, for
		# SearchSysCacheList -- use check of "allcols" to determine
		# validity
		$iih->{SysCacheIdentifier} = $inds->{with}->{syscacheid}
			if (exists($inds->{with}->{syscacheid}));

		last;
	}
	die "bad oby index: $iname"
		unless (defined($iih));

	return $iih;
} # end check_oby_index

# build hash of fixed fields for all tables/structs
sub getfixedfields
{
	my $bigh = shift;

	# known list of basic variable length types (a bit of overkill,
	# since only need to worry about bootstrap)
	my @variable = 
		qw(
 anyarray
 anytable
 bit
 bpchar
 bytea
 cidr
 inet
 int2vector
 numeric
 oidvector
 path
 polygon
 record
 refcursor
 text
 varbit
 varchar
 xml
);

	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# NOTE: Add timestamp with timezone because they make me
	# nervous -- treat them as variable length to prevent GETSTRUCT
	# issues...
#	push @variable, "time";
	push @variable, "timestamp_with_timezone";
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 

	my $varitype = join("|", @variable);

	my $ffh = {};

	while (my ($kk, $vv) = each(%{$bigh}))
	{
		my $tname = $kk;

		next # ignore comments
			if ($tname =~ m/^\_\_/);

		my $cols = [];

		my $ii;

		$ii = 0;

		my $lastfixed = 0;

		# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
		# MPP-17159: hack pg_extprotocol to deal with NULLs in fixed part
		if ($tname eq "pg_extprotocol")
		{
			$lastfixed = 1;
		}
		# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 

		for my $coldef (@{$vv->{cols}})
		{
			my $isvari = (($coldef->{sqltype} =~ m/^($varitype)$/) ||
						  # no arrays
						  ($coldef->{sqltype} =~ m/\[\]$/));

			unless ($lastfixed)
			{
				if ($isvari)
				{
					$lastfixed = $ii + 1;
				}
			}

			$ii++;

			push @{$cols}, {
				colname => $coldef->{colname},
				attnum	=> $ii,
				ctype	=> $coldef->{ctype},
				sqltype => $coldef->{sqltype},
				fixed	=> 
					($isvari || ($lastfixed && ($lastfixed <= $ii))) ? 0 : 1
			};
		}

		$ffh->{$tname} = {
			tname		   => $tname, 
			relid		   => $vv->{CamelCaseRelationId},
			maxfixedattnum => $lastfixed,
			cols		   => $cols
		};


	} # end while kk,vv

	return $ffh;
} # end getfixedfields

sub colops2typoid
{
	my $colops = shift;

	$colops =~ s/\_ops//;

	$colops = uc($colops);

	$colops .= "OID";

	$colops = "INT8OID"
		if ($colops =~ m/BIGINTOID/);

	return $colops;
}

# convert a column name to an "Anum_" column id
sub anum_key
{
	my ($tname, $wkey) = @_;

	my $atname = $tname;
	
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# XXX XXX: stupid last_operation/shoperation fixup
	if ($tname =~ m/pg\_stat\_last_(sh)?operation/)
	{
		$atname =~ s/eration$//;
		$atname =~ s/stat\_last\_/statlast/;
	}

	if ($tname =~ m/gp_distribution_policy/)
	{
		$atname =~ s/distribution\_//;
	}
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 

	return "Anum_" . $atname . "_" . $wkey;
}

sub struct_form_tname
{
	my $tname = shift;

	my $atname = $tname;
	
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# XXX XXX: stupid last_operation/shoperation fixup
	if ($tname =~ m/pg\_stat\_last_(sh)?operation/)
	{
		$atname =~ s/eration$//;
		$atname =~ s/stat\_last\_/statlast/;
			}
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 

	return "Form_" . $atname;
}

# returns true if table is a locking exception (no locks required),
# else false.  If the blacklist exists, it takes precedence over the
# whitelist.  All blacklisted tables are excluded from locking.  If
# there *isn't* a whitelist, all tables that are *not* on the
# blacklist are implicitly on the whitelist.  If there *is* a
# whitelist, all tables that are *not* on the whitelist are implicitly
# on the blacklist.
sub check_locking_exception
{
	my $tname	   = shift;

	return 0
		unless (exists($glob_glob->{lock_exceptions}));

	if (exists($glob_glob->{lock_exceptions}->{blacklist}))
	{
		return 1
			if ($glob_glob->{lock_exceptions}->{blacklist}->{$tname});
	}

	# if a white list exists, and the table is *not* on it, treat it
	# as an exception.
	if (exists($glob_glob->{lock_exceptions}->{whitelist}))
	{
		return 1
			unless ($glob_glob->{lock_exceptions}->{whitelist}->{$tname});
	}

	# if we get here:
	#    the table wasn't blacklisted.
	#    if we had a whitelist, the table must be on it (because if
	#    the table wasn't on it function should have returned TRUE
	#    already)
	return 0;

} # end check_locking_exception

# build C function for caql query expressions
sub dothing
{
	my ($bigh, $qry, $funcname, $bqh) = @_;

	my $filerr	  = "";
	my $filenames = "";

	# build list of filenames for this basic query
	if (defined($bqh) && exists($bqh->{files}))
	{
		$filenames = join("\n\t", 
						  (keys(%{$bqh->{files}})));

	}


	$filerr = "\nfiles: $filenames" 
		if (defined($filenames) && length($filenames));

	# error msg: basic query + all files that reference it
	my $qerr = $qry . $filerr;

	$funcname = "foo"
		unless (defined($funcname));

	my $tname;
	my $domethod;

	my $where1;
	my ($wkey, $wval, $wcmp);

	my $bAllEq = 1; # set false if find a non-equal comparison

	if ($qry =~ m/^\s*(delete|undef|dup|select)/i)
	{
		my @foo = 
			($qry =~ 
			 m/(delete|undef|dup|select\s+\*)\s+from\s+(\w*)(?:\s+where)?/);

		die "bad tname for $qerr" unless (2 == scalar(@foo));
		$domethod = shift @foo;

		$domethod = "select"
			if ($domethod =~ m/^select/);

		$tname = shift @foo;
	}
	elsif ($qry =~ m/^\s*insert\s+into/i)
	{
		my @foo = 
			($qry =~ 
			 m/^\s*insert\s+into\s+(\w*)/i);

		die "bad tname for $qerr" unless (1 == scalar(@foo));

		$domethod = "insert";

		$tname = shift @foo;

	}
	else
	{
		die "bad cql: $qerr";
	}

	# map the files back to the catalog tables (for pablopcatso)
	if (defined($bqh) && exists($bqh->{files}))
	{
		use File::Basename;

		for my $k1 (keys(%{$bqh->{files}}))
		{
			my $fnam = basename($k1);

			# XXX XXX: fix analyze
			if ($fnam eq "analyze.c")
			{
				if ($k1 =~ m|commands/analyze|)
				{
					$fnam = "commands/analyze";
				}
				elsif ($k1 =~ m|parser/analyze|)
				{
					$fnam = "parser/analyze";
				}
				else
				{
					die "$k1 - yet another analyze.c!";
				}
			}

			$glob_glob->{fil2tab}->{$fnam} = {}
				unless (exists($glob_glob->{fil2tab}->{$fnam}));

			$glob_glob->{fil2tab}->{$fnam}->{$tname} = 1;

		}
	}

	my $obyidx;
	# Extract index for ORDER BY if specified
	if ($qry =~ m/ORDER\_BY.*$/)
	{
		my @foo = ($qry =~ m/ORDER\_BY(.*)$/);

		$obyidx = shift @foo;

		# Trim to leave SELECT ... WHERE ...
		$qry =~ s/ORDER\_BY.*$//;

		$obyidx =~ s/^\s+//;
		$obyidx =~ s/\s+$//;
	}

	
	if ($qry =~ m/where.*$/i)
	{
		my @foo = ($qry =~ m/where\s+(.*)\s*(?:\;)?/);

		die "bad WHERE clause for $qerr" unless (scalar(@foo));

#		print Data::Dumper->Dump(\@foo);

		$where1 = shift @foo;
	}

	my (@wpred, @wpred2);

	if (defined($where1))
	{
		my $kvrex = 
			# NOTE: allow more types of comparison than equality
			#
			#    key    = (or other comparison) :bindnum
			'\s*(\w+)\s*(\=|\<\=|\>\=|\>|\<)\s*\:(\d+)\s*';

		die "bad WHERE clause for: $qerr" 
			unless ($where1 =~ m/$kvrex/);

		my @baz;

		push @baz, $where1;

		# build list of predicates if have "k1=v1 AND k2=v2..."
		if ($where1 =~ m/\s+AND\s+/i)
		{
			@baz = split(/\s+AND\s+/i, $where1);
		}

		for my $pred (@baz)
		{
			# allow key = val, key <= val, key > val, etc
			my @foo = ($pred =~ m/$kvrex/);
			die "bad WHERE clause: $where1" 
				unless (3 == scalar(@foo));

			$wkey = shift @foo;
			$wcmp = shift @foo;
			$wval = "arg" . shift @foo;

			unless ($wcmp =~ m/^\=/)
			{
				$bAllEq = 0; # not an equality comparison
			}

			push @wpred, {key => $wkey, val => $wval, cmp => $wcmp};
		}
	}

	die "no such tname $tname: $qerr" unless (exists($bigh->{$tname}));

	my $realindex  = "";
	my $uniqueidx  = 0; # index is unique
	my $allcolidx  = 0; # predicate uses all columns of index
	my $numidxcols = 0; # number of columns in index 
	my $prefixidx;      # primary key prefix index (if it exists)
	my $syscacheid = "";
	my $indexname  = "InvalidOid"; # set to realindex if find it
	my $fmgreqoid  = "";
	my $kvtransfm  = "";
	my $anumkeycol = "";
	my $relationid = "";
	my $wktyp;

	if (exists($bigh->{$tname}->{CamelCaseRelationId}) &&
		length($bigh->{$tname}->{CamelCaseRelationId}))
		
	{
		$relationid = $bigh->{$tname}->{CamelCaseRelationId};
	}
	else
	{
		die "no relationid $tname: $qerr";
	}

	$bqh->{tablename} = $tname; # for do_iud

	if (exists($bigh->{$tname}->{fk_list}))
	{
		$bqh->{foreign_key_tables} = {}
			unless (exists($bqh->{foreign_key_tables}));

		for my $fk (@{$bigh->{$tname}->{fk_list}})
		{
			my $pktname = $fk->{pktable};

			my $fkentry = "(" . join(", ", @{$fk->{pkcols}}) . ") <- (" .
				join(", ", @{$fk->{fkcols}}) . ")";

			$fkentry .= " [vector]"
				if ($fk->{type} =~ m/vector/i);

			# set of local fk cols for tname
			$bqh->{foreign_key_tables}->{$pktname} = {}
				unless (exists($bqh->{foreign_key_tables}->{$pktname}));

			$bqh->{foreign_key_tables}->{$pktname}->{$fkentry} = 1;

		}
		
	}

	if (scalar(@wpred))
	{
		for my $pred (@wpred)
		{
			$wkey = $pred->{key};

			die "no type for $wkey! : $qerr"
				unless (exists($bigh->{$tname}->{colh}->{$wkey}));

			$anumkeycol = anum_key($tname, $wkey);
			# oid column is system column (usually)

			if (($wkey eq 'oid') && 
				(exists($bigh->{$tname}->{with}->{oid})) &&
				($bigh->{$tname}->{with}->{oid}))
			{
				$anumkeycol = 'ObjectIdAttributeNumber';
			}
		
			$wktyp = $bigh->{$tname}->{colh}->{$wkey};

			# NOTE: convert regproc to oid, namedata to name
			if ($wktyp =~ m/regproc/)
			{
				$wktyp = "oid";
			}
			# XXX XXX: CHECK THIS -- from gp_fastsequence 
			if ($wktyp =~ m/bigint/)
			{
				$wktyp = "int8";
			}
		
			$fmgreqoid = "F_" . uc($wktyp) . "EQ";
			$fmgreqoid =~ s/NAMEDATA/NAME/;
			
			if ($wktyp =~ m/oid/i)
			{
				$kvtransfm = "ObjectIdGetDatum";
			}
			if ($wktyp =~ m/name/i)
			{
				$kvtransfm = "NameGetDatum";
			}
			if ($wktyp =~ m/int2/i)
			{
				$kvtransfm = "Int16GetDatum";
			}

			$pred->{anumkeycol} = $anumkeycol;
			$pred->{fmgreqoid}	= $fmgreqoid;
			$pred->{wktyp}		= $wktyp;
			$pred->{kvtransfm}	= $kvtransfm;
		} # end for my pred

		my $iih  = choose_index($bigh, $tname, \@wpred);

		# if we had an ORDER BY, use that matching index instead
		if (defined($obyidx))
		{
			$iih = check_oby_index($bigh, $tname, $obyidx, \@wpred);
		}

		if (defined($iih))
		{
			$realindex = $iih->{CamelCaseIndexId}
				if (exists($iih->{CamelCaseIndexId}));
			$syscacheid = $iih->{SysCacheIdentifier}
				if (exists($iih->{SysCacheIdentifier}));
			$uniqueidx = 1
				if (exists($iih->{unique}));
			$allcolidx = 1
				if (exists($iih->{allcols}));

			die "bad index -- no numcols"
				unless (exists($iih->{numcols}));
			$numidxcols = $iih->{numcols};
		}
		else
		{
			$realindex	= undef;
			$syscacheid = undef;
		}

		if (defined($realindex))
		{
			$bqh->{func_index} = $realindex;
		}
		else
		{
			$realindex  = "";
		}

		# rebuild the WHERE predicate list.  If we have an index,
		# re-order it, else just use the original

		if (!length($realindex))
		{
			push @wpred2, @wpred;
		}
		else
		{
			my $numkeys = scalar(@wpred);
			
			my %pkeyh; # hash of key col names
			for my $pred (@wpred)
			{
				$pkeyh{$pred->{key}} = $pred;
			}
			
			for my $inds (@{$bigh->{$tname}->{indexes}})
			{
				next
					unless ($realindex eq $inds->{CamelCaseIndexId});
				
				# NOTE: rebuild the WHERE clause predicate column list in
				# index key column order
				for my $jj (0..($numkeys-1))
				{
					my $icol = $inds->{cols}->[$jj];
					
					$wpred2[$jj] = $pkeyh{$icol->[0]};
				}
				last;
			}
		}

		# if this table is a "dependent class" of the parent table,
		# then we can lock the primary key of the parent if we aren't
		# using all the columns of our index.
		# NOTE: first column must have equality comparison, but strict
		# equality for all columns is not necessary
		if (defined($iih) &&
			($wpred2[0]->{cmp} =~ m/^\=/) &&
			length($realindex) && 
			!($uniqueidx && $allcolidx) &&
			(exists($iih->{primarykey_prefix})))
		{
			$prefixidx = $iih->{primarykey_prefix};
			
			$bqh->{func_note} .= 
				"index " . $prefixidx->{pktname} . "(" .
				$prefixidx->{pkcolname} . ") <" .
				$prefixidx->{pkidx} . "> is a prefix of \n\t" .
				"index " . $tname . "(" .
				$prefixidx->{fkcolname} . ", ...) <" .
				$realindex . ">\n";
		}

	} # end if scalar wpred

	my $funcargs = "cqContext *pCtx, cq_list *pcql, bool bLockEntireTable";
	my $indexok = "pCtx->cq_useidxOK";

	if (scalar(@wpred2))
	{
		if (length($realindex))
		{
			$indexname = $realindex;
		}
		else
		{
			$indexok = "false";
		}
	}
	else
	{
		$indexok = "false";
	}

	# for optional arguments, strip out these lines from the final string
	my $stripout = '/* **stripout** */';
#	my $stripout = '';
	my $striprex = quotemeta($stripout);

	my $basicargs =
		{
			FUNCNAME	 => $funcname,
			FUNCARGS	 => $funcargs,
			INDEX		 => $indexname,
			INDEXOK		 => $indexok,
			UNIQUEIDX	 => $uniqueidx,
			KEY			 => "pCtx->cq_scanKeys",
			KEYNUM		 => scalar(@wpred2),
			REL			 => "rel",
			SCAN		 => "scan",
			TUPLE		 => "tuple",
#			SNAPSHOT	 => "SnapshotNow", 
			SNAPSHOT	 => "pCtx->cq_snapshot", 
			RELATIONID	 => $relationid,
			RELATIONDEF	 => $stripout,
			HEAPTUPLEDEF => $stripout,
			OPENREL		 => $stripout,
			CLOSEREL	 => $stripout,
			KEYVAL		 => $wval
		};

	# turn on lock checking if lockcheck is on 
	$basicargs->{RELLOCKTEST} = (exists($glob_glob->{lockcheck}) &&
								 $glob_glob->{lockcheck}) ?
								 rel_lock_test() : $stripout;


	my $scankeyinit = "";
	my $readpk_init = ""; # readpk and lw_colstr for primary key readlock
	my $lw_colstr   = "";

	if ($domethod !~ m/sel/i)
	{
		$basicargs->{HEAPTUPLEDEF} = "HeapTuple    tuple;";
	} 

	for my $ii (0..(scalar(@wpred2)-1))
	{
		my $pred2 = $wpred2[$ii];
		my $currscankey;

		$wkey  = $pred2->{key};
		$wval  = $pred2->{val};
		$wcmp  = $pred2->{cmp};

		$basicargs->{KEYOFFSET}	  = $ii;
		$basicargs->{ANUMKEYCOL}  = $pred2->{anumkeycol};
		$basicargs->{FMGREQOID}	  = $pred2->{fmgreqoid};
		$basicargs->{KVTRANSFORM} = $pred2->{kvtransfm};
		# build type oid for caql_pkhash()
		$basicargs->{MHTYPOID}    = uc($pred2->{wktyp}) . "OID";
		$basicargs->{MHTYPOID}    =~ s/NAMEDATA/NAME/; # NAMEOID
		
		if ($domethod =~ m/sel/i)
		{
			$basicargs->{KVTRANSFORM} = "";
			$basicargs->{KEYVAL} = "pCtx->cq_datumKeys[$ii]";
		} 

		# build a ScanKeyInit(...) statement
		$currscankey =  		
			doformat(scankeyinit(),
					 $basicargs
			);

		# fix comparison for inequality
		unless ($wcmp =~ m/^\=/)
		{
			if ($wcmp =~ m/^\<\=/)
			{
				$currscankey =~
					s/BTEqualStrategyNumber/BTLessEqualStrategyNumber/;
				$currscankey =~ s/EQ\,/LE,/;
			}
			elsif ($wcmp =~ m/^\>\=/)
			{
				$currscankey =~ 
					s/BTEqualStrategyNumber/BTGreaterEqualStrategyNumber/;
				$currscankey =~ s/EQ\,/GE,/;

			}
			elsif ($wcmp =~ m/^\</)
			{
				$currscankey =~
					s/BTEqualStrategyNumber/BTLessStrategyNumber/;
				$currscankey =~ s/EQ\,/LT,/;

			}
			elsif ($wcmp =~ m/^\>/)
			{
				$currscankey =~ 
					s/BTEqualStrategyNumber/BTGreaterStrategyNumber/;
				$currscankey =~ s/EQ\,/GT,/;
			}
		}

		# NOTE: special case for "primarykey_prefix"/"dependent class"
		# -- build the caql_pkhash(...) statement for a single column
		if (defined($prefixidx) && 
			!length($lw_colstr))
		{
			# NOTE: only one column for this case, and we store the
			# results in prefixidx (instead of readpk_init). 

			$prefixidx->{READPK_INIT} = 
				doformat(readpkeyhashinit(),
						 $basicargs
				);
		}

		# build a "column string" for caql_lockwell()
		$lw_colstr .= ", " if (length($lw_colstr));
		$lw_colstr .= $wkey;

		# initialize the hash for the primary key lock 
		# -- build the caql_pkhash(...) statement
		$readpk_init .= 
			doformat(readpkeyhashinit(),
					 $basicargs
			);

		$scankeyinit .= $currscankey;

		# XXX XXX: do something about stupid KEYVAL swizzling...
		$basicargs->{KEYVAL} = $wval;

		$scankeyinit .= "\n";


	} # end for my ii

	$scankeyinit .= "\n";

	# store the ScanKeyInit(...) and caql_pkhash(...) initializations
	$basicargs->{SCANKEYINIT} = $scankeyinit;
	$basicargs->{READPK_INIT} = $readpk_init;

	# special case if can use SysCache to satisfy query
	$basicargs->{SYSCACHEIDENTIFIER} = $syscacheid;
	$basicargs->{NUMSYSCACHEIDXCOLS} = $numidxcols;
	
	if (!$bAllEq)
	{
		$bqh->{func_note} .= "WHERE clause is not strict equality\n";
		if (defined($syscacheid) && length($syscacheid))
		{
			$bqh->{func_note} .= "Could not use syscache due to inequality\n";
		}
	}
	else
	{
		# NOTE: even if WHERE clause is strict equality, syscache is
		# only valid if predicate matches *all* columns of index, 
		# *or* if basic function is invoked by SearchSysCacheList()

		if (defined($syscacheid) && length($syscacheid) && !$allcolidx)
		{
			$bqh->{func_note} .= 
				"Predicate does not match all index columns " .
				"( " . scalar(@wpred2) . " != " . $numidxcols . " ),\n" .
				"can only use syscache for SearchSysCacheList case\n";
		}

	}

	# logging code
	if ($glob_glob->{logquery} || $glob_glob->{logquery_hash})
	{
		$basicargs->{SELECTFROM_ELOG} = 
			doformat(selectfrom_elog(),
					 $basicargs,
			);
	}
	else
	{
		$basicargs->{SELECTFROM_ELOG} = $stripout;
	}

	# set up read locks if possible
	if (!(exists($glob_glob->{readlock}) && $glob_glob->{readlock}))
	{
		$basicargs->{GET_READPK}		   = $stripout;
		$basicargs->{READPK_ENTIRETABLE}   = $stripout;
		$basicargs->{INSERTPK_ENTIRETABLE} = $stripout;
	}
	else # build read locks
	{
		my $bException = 0;

		# XXX XXX NOTE: locking exceptions
		# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
		# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
		# XXX XXX: stupid last_operation/shoperation fixup, etc
		$bException = check_locking_exception($tname);

		# build caql_lock_entiretable(...) statement for insert.
		#
		# if the table has any primary key, just need a share lock on
		# the entire table, else need an exclusive lock first
		#
		if (!$bException &&
			($domethod =~ m/insert/i))
		{
			if (exists($bigh->{$tname}->{calico}) &&
				exists($bigh->{$tname}->{calico}->{pkeys}) &&
				(scalar(@{$bigh->{$tname}->{calico}->{pkeys}}) > 0))
			{
				# get share lock on entire table (if necessary)
				$basicargs->{INSERTPK_ENTIRETABLE} = 
					doformat(readinsert_entiretable_share(),
							 $basicargs,
					);
			}
			else
			{
				$basicargs->{READPK_FAILREASON}	 = 
					"no primary keys for $tname";
				$basicargs->{INSERTPK_ENTIRETABLE} = 
					doformat(readinsert_entiretable_exclusive(),
							 $basicargs,
					);
				$bqh->{func_note} .= 
					"WARNING: insert operation not protected with pk lock!!\n";

				$bigh->{$tname}->{calico}->{lock_entire_table} = 1;
			}
		}

		if ($bException)
		{
			$basicargs->{READPK_FAILREASON}	 = "exception for $tname";
			$basicargs->{GET_READPK}		 = 
				"/* no table lock - exception for $tname */\n";
			$basicargs->{READPK_ENTIRETABLE} = 
				"/* no table lock - exception for $tname */\n";
			$basicargs->{INSERTPK_ENTIRETABLE} = 
				"/* no table lock - exception for $tname */\n";
		}
		elsif (!defined($prefixidx) && 
			   (!$bAllEq || !length($readpk_init) || 
				(!$uniqueidx) || (!$allcolidx)
			   ))
		{
			if (!length($readpk_init) || !length($realindex))
			{
				$basicargs->{READPK_FAILREASON} = "no primary key index";
			}
			elsif (!$uniqueidx)
			{
				$basicargs->{READPK_FAILREASON} = "index not unique";
			}
			elsif (!$allcolidx)
			{
				$basicargs->{READPK_FAILREASON} = "partial match of index cols";
			}
			elsif (!$bAllEq)
			{
				$basicargs->{READPK_FAILREASON} = "inequality in index lookup";
			}
			$basicargs->{GET_READPK} = 
				doformat(readinsert_entiretable_exclusive(),
						 $basicargs,
				);

			# NOTE: don't consider "tablename" exceptions for locking
			# case -- they are protected by other locks
			if (($bqh->{num_upd_ops} || $bqh->{num_del_ops}) && 
				!$bException &&
				(!$bAllEq || !length($readpk_init) || 
				(!$uniqueidx) || (!$allcolidx)
				))
			{
				$bqh->{func_note} .= 
					"WARNING: update/delete operation not protected with pk lock!!\n";
				$bigh->{$tname}->{calico}->{lock_entire_table} = 1;
			}

		}
		else
		{
			my $real_iname = $basicargs->{INDEX};
			my $real_relid = $basicargs->{RELATIONID};

			# build caql_lock_entiretable(...) statement
			$basicargs->{READPK_ENTIRETABLE} = 
				doformat(readinsert_entiretable_share(),
						 $basicargs,
				);

			if (!defined($prefixidx))
			{
				$basicargs->{LW_TNAME} = $tname;
				$basicargs->{COLSTR}   = $lw_colstr;
			}
			else
			{
				$basicargs->{INDEX}		  = $prefixidx->{pkidx};
				$basicargs->{RELATIONID}  = $prefixidx->{pkrelid};
				$basicargs->{LW_TNAME}	  = $prefixidx->{pktname};
				$basicargs->{COLSTR}	  = $prefixidx->{pkcolname};
				$basicargs->{READPK_INIT} =	$prefixidx->{READPK_INIT};

				$bqh->{func_note} .= "Primary key Locking using index " .
					$prefixidx->{pkidx} . " on " . 
					$prefixidx->{pktname} . "\n\tinstead of " .
					$real_iname . "\n";
			}

			$basicargs->{GET_READPK} = 
				doformat(get_readpk(),
						 $basicargs,
				);
			
			# restore the index name and relation id
			$basicargs->{INDEX} = $real_iname;
			$basicargs->{RELATIONID} = $real_relid;
		}
	}

	# can only use syscache for equality primary key lookup
	if (defined($syscacheid) && length($syscacheid) && $bAllEq)
	{
		$bqh->{syscacheid} = $syscacheid;

		$basicargs->{SYSCACHECHECK} =
			doformat(syscacheid(),
					 $basicargs,
			);
		# and special tuple descriptors
		$basicargs->{SYSCACHETUPDESC} =
			doformat(syscachetupdesc(),
					 $basicargs,
			);

	}
	else # must not use SysCache
	{
		$basicargs->{SYSCACHECHECK} =
			doformat(nosyscacheid(),
					 $basicargs,
			);
		$basicargs->{SYSCACHETUPDESC} = "";
	}

	# add the indexOK check for non-heapscan case
	if ($indexok !~ m/false/)
	{
		$basicargs->{SYSCACHECHECK} .= 
			doformat(idxokfunc(),
					 $basicargs,
			);
	}

	$basicargs->{SCANHDR} =
 		doformat(scanhdr(),
				 $basicargs,
		);


	# cleanup whitespace in scanheader
	$basicargs->{SCANHDR} =~ s/^\s*$striprex\s*$//gm;
	$basicargs->{SCANHDR} =~ s/^(\s*)$striprex(.*)$/$1$2/gm;
	$basicargs->{SCANHDR} =~ s/^(\s*)$//gm;

	$basicargs->{BEGINSCAN} =
 		doformat(beginscan(),
				 $basicargs,
		);
	$basicargs->{NEXTSCAN} =
 		doformat(nextscan(),
				 $basicargs,
		);
	$basicargs->{ENDSCAN} =
 		doformat(endscan(),
				 $basicargs,
		);

	my $bigstr;

	if ($domethod =~ m/sel/i)
	{
		$bigstr = 
			doformat(selectfrom(),
					 $basicargs
			);
	}
	if ($domethod =~ m/del/i)
	{
		$bigstr = 
			doformat(deletefrom(),
					 $basicargs
			);
	}
	elsif ($domethod =~ m/dup/i)
	{
		$bigstr = 
			doformat(duplicate_obj(),
					 $basicargs
			);
	}
	elsif ($domethod =~ m/undef/i)
	{
		$bigstr = 
			doformat(undef_obj(),
					 $basicargs
			);
	}
	elsif ($domethod =~ m/insert/i)
	{
		$bigstr = 
			doformat(insertinto(),
					 $basicargs
			);
	}


	$bigstr =~ s/^\s*$striprex\s*$//gm;
	$bigstr =~ s/^(\s*)$striprex(.*)$/$1$2/gm;

	return $bigstr;
} # end dothing

# build insert/update/delete validation functions
sub do_iud
{
	my ($bigh, $qry, $funcname, $bqh) = @_;

	my @fktablist;
	my @pktablist;

	my @uniqidxlist;	# uniq index list "indexname: col1, col2..."
	my %uniqidxh;		# hash by idx name

	my $tname  = $bqh->{tablename};
	my $trelid = $bigh->{$tname}->{CamelCaseRelationId};

	my $shlockwell_str = "/* No Share Locks Acquired */";

	if (exists($bqh->{foreign_key_tables}))
	{
		for my $fktab (sort(keys(%{$bqh->{foreign_key_tables}})))
		{
			# XXX XXX NOTE: IUD Exceptions - fix these!!
			# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
			# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
			# XXX XXX: gp_relation_node
			if ($fktab =~ m/(gp_relation_node)/)
			{
				next;
			}

			for my $fkentry (sort(keys(%{$bqh->{foreign_key_tables}->{$fktab}})))
			{
				push @fktablist, 
					$fktab . " " . $fkentry;
			}
		}
	}

	my $idxstr = "/* ZERO indexes */";
	my $xlockwell_str = "/* Cannot obtain exclusive lock on tuple !! */";

	if (exists($bigh->{$tname}))
	{
		# NOTE: built in bigh_fk_fixup()
		if (exists($bigh->{$tname}->{pk_dependent}))
		{
			for my $pktab (sort(keys(%{$bigh->{$tname}->{pk_dependent}})))
			{
				# XXX XXX NOTE: IUD Exceptions - fix these!!
				# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
				# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
				# XXX XXX: gp_relation_node
				if ($pktab =~ m/(gp_relation_node)/)
				{
					next;
				}

				# potentially multiple fk references to tname in
				# dependent table
				for my $pkl (sort(keys(%{$bigh->{$tname}->{pk_dependent}->{$pktab}})))
				{
					push @pktablist, 
					$pktab . " " . $pkl;
				}
			}
		}

		if (exists($bigh->{$tname}->{indexes}))
		{
			for my $idx (@{$bigh->{$tname}->{indexes}})
			{
				if (exists($idx->{unique})
					&& length($idx->{unique})
					&& ($idx->{unique}))
				{
					my $colstr;
					my $cola = [];
					
					$colstr = "";

					for my $col (@{$idx->{cols}})
					{
						$colstr .= ", "
							if (length($colstr));
						
						$colstr .= $col->[0];

						push @{$cola}, { name => $col->[0], ops => $col->[1] };
					}

					$colstr = $idx->{CamelCaseIndexId} . ": " . $colstr;

					push @uniqidxlist, $colstr;

					$uniqidxh{$idx->{CamelCaseIndexId}} = $cola;
				}

			}

			if (scalar(@uniqidxlist))
			{
				$idxstr = "/*\n" . 
					scalar(@uniqidxlist) . " unique index";
				# plural
				$idxstr .=
					((scalar(@uniqidxlist) > 1) ? "es:\n" : ":\n");

				$idxstr .= join("\n", @uniqidxlist) . "\n*/";

				$xlockwell_str = "";

				for my $idxl (@uniqidxlist)
				{
					my $lockwellidxid;

					#
					# indexid:<space> key1[, key2...]
					#

					my @zzz = split(/\:\s*/, $idxl, 2);

					die "bad index list: $idxl"
						unless (2 == scalar(@zzz));

					$lockwellidxid = $zzz[0];
					$idxl = $zzz[1];

					die "bad index: $lockwellidxid"
						unless (exists($uniqidxh{$lockwellidxid}));

					my $lw_makehash = "";
					my $colinfo = $uniqidxh{$lockwellidxid};
					for my $cola (@{$colinfo})
					{
						$lw_makehash .= 
							doformat(caql_makehash(),
									 {
										 MHTYPOID => 
											 colops2typoid($cola->{ops}),

										 MHATTNUM => 
											 (("oid" eq $cola->{name}) ? 
											  'ObjectIdAttributeNumber' : 
											  anum_key($tname, $cola->{name}))
									 }
							);
					}


					my $lockwellh = {
#						TABLERELID => "RelationGetRelid(pCtx->cq_heap_rel)",
						TABLERELID => $bigh->{$tname}->{CamelCaseRelationId},
#						LOCKMODE => "AccessExclusiveLock",
						LOCKMODE => "pklockmode",
						LW_TNAME =>  $tname,
						COLSTR =>  $idxl,
						LOCKWELL_IDX => $lockwellidxid,
						LOCKWELL_MAKEHASH => $lw_makehash,
					};

					# build a string, but don't specify tuple
					my $basic_lockwell_str .= 
						doformat(caql_iud_lockwell(),
								 $lockwellh);
					
					# build two strings -- one for newtup, other for oldtup
					$lockwellh->{TUPLE} = "newtup";
					$lockwellh->{HASHVAR} = "newhash";
					$lockwellh->{LOCKWELL_HASHKEY} = "newhash";
					$lockwellh->{LOCKWELL_HASHKEY2} = "newhash";
					
					my $l1 = 
						doformat($basic_lockwell_str,
								 $lockwellh);
					
					$lockwellh->{TUPLE} = "oldtup";
					$lockwellh->{HASHVAR} = "oldhash";
					$lockwellh->{LOCKWELL_HASHKEY} = "oldhash";
					$lockwellh->{LOCKWELL_HASHKEY2} = "oldhash";
					
					my $l2 = 
						doformat($basic_lockwell_str,
								 $lockwellh);
					
					$xlockwell_str .= $l1 . $l2;

				} # end for my idxl
					
			}
			else
			{
				$idxstr = "/*\nZERO unique indexes\n*/";
			}

		}

	}

	my $rex1 = '(gp\_distribution\_policy)|(pg\_authid)';

	# XXX XXX NOTE: IUD Exceptions
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# XXX XXX: stupid last_operation/shoperation fixup
	if ($tname =~ m/pg\_stat\_last\_(sh)?operation/)
	{
		@fktablist = ();
		@pktablist = ();
		$xlockwell_str = "/* $tname: do not get exclusive lock */";
	}
	if ($tname =~ m/$rex1/)
	{
		$xlockwell_str = "/* $tname: do not get exclusive lock */";
	}
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 

	if (scalar(@fktablist) &&
		($tname !~ m/$rex1/))
	{
		$shlockwell_str = "";

		for my $fktab1 (@fktablist)
		{
			my $fktab = $fktab1 . ""; # copy the string so we can modify it

			# XXX XXX: special case for oidvector or oid array
			my $isvector = ($fktab =~ m/\[vector\]/);

			$fktab =~ s/\s*\[vector\]\s*//; # remove it

			#
			# tablename<space>(pk1[, pk2...]) <- (fk1[, fk2...])
			#
			my @foo = split(/\s+/, $fktab, 2); # first part is tablename

			die "bad fktab: $fktab"
				unless (2 == scalar(@foo));

			# tricky nomenclature -- tname is our table, fktname is the
			# foreign key table, but we need the primary key of that table.
			my $fktname = $foo[0];

			my $fkrelid = $bigh->{$fktname}->{CamelCaseRelationId};

			#
			# (pk1[, pk2...]) <- (fk1[, fk2...])
			#

			# get key list (pk/fk)
			my @baz = split(/\s+\<\-\s+/, $foo[1], 2);

			die "bad fktab2: $fktab"
				unless (2 == scalar(@baz));
			
			my $pk_colstr = $baz[0];
			# remove lead/trailing parentheses 
			$pk_colstr =~ s/^\s*\(\s*//;
			$pk_colstr =~ s/\s*\)\s*$//;

			my @lockwell_keys;
			my @ikeylist;
			my $idxl = $baz[1];

			# remove lead/trailing parentheses 
			$idxl =~ s/^\s*\(\s*//;			
			$idxl =~ s/\s*\)\s*$//;			

			if ($idxl =~ m/\,/)
			{
				@ikeylist = split(/\s*\,\s*/, $idxl)
			}
			else
			{
				# single key
				push @ikeylist, $idxl;
			}

			# need the type of vector (oidvector or oid array) in
			# order to distinguish the DatumGet<blah>() method in
			# caql_iud_lockwell_oidvector()
			my $isvector_type;

			if ($isvector)
			{
				die "invalid vector has > 1 col"
					unless (1 == scalar(@ikeylist));
				# get the type by colname (only one for this case)
				$isvector_type = $bigh->{$tname}->{colh}->{$ikeylist[0]};
			}

			# NOTE: 5 keys max
			for my $ii (0..4)
			{
				if (defined($ikeylist[$ii]))
				{
					my $kk = $ikeylist[$ii];
					$kk =~ s/^\s+//;
					$kk =~ s/\s+$//;
					if ($kk =~ m/^oid$/)
					{
						# Oid column
						$kk = "ObjectIdAttributeNumber";
					}
					else
					{
						# attribute column
						$kk = anum_key($tname, $kk);
					}
					push @lockwell_keys, $kk;
				}
				else
				{
					push @lockwell_keys, "InvalidAttrNumber";
				}
			}

			# find the indexid for the unique index with those columns
			my $lockwellidxid; 
			my $lockwellidxid_a = 
				unique_index_by_cols($bigh,
									 $fktname,
									 $pk_colstr
				);

			my $lw_makehash = "{ d = 0; isnull = 0; }";

			if (defined($lockwellidxid_a))
			{
				my $colinfo    = $lockwellidxid_a->[1];
				$lockwellidxid = $lockwellidxid_a->[0];
				$lw_makehash = "";

				if (!$isvector)
				{
					for my $cola (@{$colinfo})
					{
						my $attnum = shift @lockwell_keys;
						
						$lw_makehash .= 
							doformat(caql_makehash(),
									 {
										 MHTYPOID => 
											 colops2typoid($cola->{ops}),
										 MHATTNUM => $attnum
									 }
							);
					}
				}
				else
				{
					# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
					# NOTE: the vector case is slightly funky --
					# we build the LOCKWELL_MAKEHASH def for
					# caql_iud_lockwell_oidvector(), but it's slightly
					# different from the caql_makehash() definition
					# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
					my $attnum = shift @lockwell_keys;

					$lw_makehash .= 
							doformat(
						"d = caql_getattr_internal(pCtx, {TUPLE}, \n" .
						"				 {MHATTNUM},\n" .
						"				 &isnull);\n" ,
									 {
										 MHATTNUM => $attnum
									 }
							);
				}
			}
			else
			{
				# no index found
				$lockwellidxid = "InvalidOid";
			}

			my $lockwellh  = {
				TABLERELID		  => $fkrelid,
				LOCKMODE		  => "AccessShareLock",
				LW_TNAME		  => $fktname,
				COLSTR			  => $pk_colstr,
				LOCKWELL_IDX	  => $lockwellidxid,
				LOCKWELL_MAKEHASH => $lw_makehash,
				LW_GETDATUM		  => "DatumGetPointer", # for oidvector case
				LW_VEC_OR_ARRAY	  => "vector",			# for oidvector case.
			};

			# NOTE: oidvector is fixed length, 
			# but oid array *could* be toasted...
			if ($isvector)
			{
				# if not oidvector, must be array (eg "Oid[1]")
				if ($isvector_type !~ /vector/i)
				{
					$lockwellh->{LW_GETDATUM}	  = "DatumGetArrayTypeP";
					$lockwellh->{LW_VEC_OR_ARRAY} = " array";
				}
			}

			# build a string, but don't specify tuple
			my $basic_lockwell_str .= 
				doformat(($isvector ? 
						  caql_iud_lockwell_oidvector() : 
						  caql_iud_lockwell()),
						 $lockwellh);
					
			# build two strings -- one for newtup, other for oldtup
			$lockwellh->{TUPLE} = "newtup";
			$lockwellh->{HASHVAR} = "newhash";
			$lockwellh->{LOCKWELL_HASHKEY} = "newhash";
			$lockwellh->{LOCKWELL_HASHKEY2} = "newhash";
					
			my $l1 = 
				doformat($basic_lockwell_str,
						 $lockwellh);
					
			$lockwellh->{TUPLE} = "oldtup";
			$lockwellh->{HASHVAR} = "oldhash";
			$lockwellh->{LOCKWELL_HASHKEY} = "oldhash";
			$lockwellh->{LOCKWELL_HASHKEY2} = "oldhash";
			
			my $l2 = 
				doformat($basic_lockwell_str,
						 $lockwellh);
					
			$shlockwell_str .= $l1 . $l2;
		} # end for my fktab
	}

	my $bh_str = doformat(
		caql_iud_buildhash(),
		{
			IUD_BH_TUP	  => "oldtup",
			IUD_BH_VAL	  => "oldhash"
		});
		$bh_str .= doformat(
		caql_iud_buildhash(),
		{
			IUD_BH_TUP	  => "newtup",
			IUD_BH_VAL	  => "newhash"
		});

	my $iud_args = {
		IUD_FUNC_NAME	  => $funcname,
		IUD_TNAME		  => $tname,
		IUD_BUILDHASH	  => $bh_str,
		IUD_FKTABS		  => join("\n", @fktablist),
		IUD_PKTABS		  => join("\n", @pktablist),
		IUD_IDX			  => $idxstr,
		IUD_IDX_XLOCKWELL => $xlockwell_str,   # exclusive locks
		IUD_SHLOCKWELL	  => $shlockwell_str,  # share locks
	};

	$iud_args->{IUD_DELETE} = "";

	# for INSERT only, don't construct a delete case
##	if ($qry !~ m/\s*insert/i)
	if (1) # XXX XXX XXX XXX XXX
	{
		if (scalar(@pktablist))
		{
			# have some foreign keys
			$iud_args->{IUD_DELETE} = 
				doformat(caql_iud_delete(),
						 $iud_args);

			$iud_args->{IUD_DELETE} .= "\n";
			for my $pktab (@pktablist)
			{
				# XXX XXX:
				next if ($pktab =~ m/\[vector\]/);

				my $pkrelid = $bigh->{$pktab}->{CamelCaseRelationId};

#				$iud_args->{IUD_DELETE} .= "gm = LockRelationOid_GetGrantMask($pkrelid, \"$funcname\");\n";
			}

			
		}
		else
		{
			# none
		}
	}

	$iud_args->{IUD_INSERTUPDATE} = "";

	if (scalar(@fktablist))
	{
		$iud_args->{IUD_INSERTUPDATE} = 
			doformat(caql_iud_insertupdate(),
					 $iud_args);

		$iud_args->{IUD_INSERTUPDATE} .= "\n";

		$iud_args->{IUD_DELETE} = 
			"/* DELETE: no tables have fk reference to $tname */"
			unless (scalar(@fktablist));

	}
	else
	{
		# none
		if (scalar(@pktablist))
		{
			$iud_args->{IUD_INSERTUPDATE} = 
			"/* INSERT/UPDATE: $tname does not have fk reference to any table */"

		}
		else
		{
			$iud_args->{IUD_INSERTUPDATE} = 
			"/* INSERT/UPDATE/DELETE: no references\n   $tname is unrelated to any other table */"
		}
	}


	my $bigstr =
			doformat(caql_iud_function(),
					 $iud_args);

	return $bigstr;
} # end do_iud


sub do_interactive
{
	my $bigh = shift;

	while (<>)
	{
		my $ini = $_;

		next unless (length($ini));
		next if ($ini =~ m/^\s*$/);

		print dothing($bigh, $ini);
	}
}

sub check_bind_variables
{
	my ($qry, $arglst) = @_;
	use Text::ParseWords;

	# find bind variables (:1 ... :N ), where max N = 5, if they
	# exist
	my @zzz = split(/(\:\d+)/, $qry);

	my @bindlst;

	for my $tok (@zzz)
	{
		next unless ($tok =~ m/^\:\d+$/);
		$tok =~ s/\://;
		
		if (($tok < 1) || ($tok > 5))
		{
			warn("bind variable $tok out of range (1-5)");
			return 0;
		}

		if (defined($bindlst[$tok-1]))
		{
			warn("Duplicate bind variable $tok");
			return 0;
		}
		$bindlst[$tok-1] = "CHECK_DUMMY";
	}

	for my $ii (1..(scalar(@bindlst)))
	{
		my $bb = $bindlst[$ii-1];

		if (!defined($bb) ||
			($bb !~ m/CHECK\_DUMMY/))
		{
			warn("missing bind variable $ii");
			return 0;
		}

	}

	my @args;

	if (defined($arglst) && length($arglst) && ($arglst =~ m/\,/))
	{
		# split by comma, but use Text::ParseWords::parse_line to
		# preserve quoted descriptions
		@args = parse_line(",", 1, $arglst);

		# Note: first comma is delimiter for *start* of arglst, so
		# first arg should be NULL/blank
		my $a1 = shift @args;
		if (defined($a1) && ($a1 =~ m/\w/))
		{
			warn("weird first argument $a1");
			return 0;
		}

	}
	
	if (scalar(@args))
	{
		if ($args[0] =~ m/^\s*\".*\"/)
		{
			my $a2 = $args[0];
			$a2 =~ s/^\s*//;
			warn("Possible comma problem -- should first arg $a2 be part of query?");
			return 0;
		}

		if (!scalar(@bindlst))
		{
			my $a2 = $args[0];
			$a2 =~ s/^\s*//;
			# XXX XXX: allow NULL as placeholder for non-existent bind
			# vars to deal with compilation issues with cql->cql1 (ie,
			# have a trailing comma preceding the non-existent
			# varargs).
			unless ($a2 =~ m/^NULL/)
			{
				warn("Found arg $a2 but no bind variables");
				return 0;
			}
		}

	}

	# XXX XXX: Cannot do validation until return entire arglist
	# (versus stopping at first end paren...)
	if (scalar(@args) != scalar(@bindlst))
	{
		my $argnum = scalar(@args);
		my $bindnum = scalar(@bindlst);

#		print Data::Dumper->Dump(\@bindlst);
#		print Data::Dumper->Dump(\@args);

#		warn ("arg mismatch: $bindnum bind variables and $argnum arguments");
#		return 0;
	}


	return 1;
} # end check_bind_variables

# extract caql from c source file
sub get_caql
{
	my ($caqlh, $filnam, $bigh) = @_;

	die "bad file $filnam" unless (-e $filnam);

	my $whole_file;

    {
        # $$$ $$$ undefine input record separator (\n)
        # and slurp entire file into variable

        local $/;
        undef $/;

		my $fh;

		open $fh, "< $filnam" or die "cannot open $filnam: $!";

		$whole_file = <$fh>;

		close $fh;
    }
	
	# find "cql(.*)", where ".*" is really "[not a close paren]*"

	# NOTE: "count(*)" in the select list messes up this pattern, so
	# replace it with a dummy expression, then extract the cql, then
	# replace the dummy with "count(*)"
	
#	my @foo = ($whole_file =~ m/(cql0\((?:[^\)])*\))/g);

	# XXX XXX: Ugh!  need to do this in a way to handle mixed case. Or
	# complain or something
	$whole_file =~ s/count\s*\(\s*\*\s*\)/CALICO_DUMMY_COUNTSTARR/g;
	$whole_file =~ s/COUNT\s*\(\s*\*\s*\)/CALICO_DUMMY_COUNTSTAR2/g;
	my @foo = ($whole_file =~ m/(cql(?:0)?\((?:[^\)])*\))/g);

	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	# NOTE: DOES NOT FIND full cql(.*) expression -- it may
	# terminate at the closing paren for the first argument, 
	# eg: ObjectIdGetDatum(roleid)
	# TODO: beef up the regex match to process the whole arg list.
	# It's not as simple as looking for a trailing semicolon, since
	# cql statements can be located in conditional expressions.
	# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 

#	print Data::Dumper->Dump(\@foo);

	my @baz;

	for my $lin (@foo)
	{
#		$lin =~ s/\"\s*$^\s*"//gm;

		# remove newlines and append contiguous quoted strings into a
		# single string
		$lin =~ s/\n//gm;
		$lin =~ s/\"\s*\"//g;

		# replace the dummy with "count(*)"
		$lin =~ s/CALICO_DUMMY_COUNTSTARR/count\(\*\)/g;
		$lin =~ s/CALICO_DUMMY_COUNTSTAR2/COUNT\(\*\)/g;

		push @baz, $lin;
	}

#	print Data::Dumper->Dump(\@baz);

	die "duplicate filename $filnam"
		if (exists($caqlh->{files}->{$filnam}));

	$caqlh->{files}->{$filnam}->{rawtxt} = \@foo;
	$caqlh->{files}->{$filnam}->{cookedtxt} = \@baz;

	# line should look like cql("<caql>"[, arg1 [,argN...]]
	# NOTE: no trailing close paren ")" 
	for my $lin (@baz)
	{
#		my @zzz = ($lin =~ m/\s*cql0\(\"(.*)/);
		my @zzz = ($lin =~ m/\s*cql(?:0)?\(\"(.*)\"/); 

		die "bad caql in $filnam: $lin"
			unless (scalar(@zzz));

		my $qry = shift @zzz;

		# check if have sufficient/excess args, because C varargs is
		# too dumb
		@zzz = split(/\"/, $lin, 3);
		# zzz[0] is 'cql("'
		# zzz[1] is qry
		my $arglst = $zzz[2];

		die "bad caql in $filnam: $lin"
			unless (check_bind_variables($qry, $arglst));

		$caqlh->{queries}->{$qry} = { files => {}, cql => "cql0" }
		unless (exists($caqlh->{queries}->{$qry}));

		# detect case of pure cql0 queries
		if ($caqlh->{queries}->{$qry}->{cql} eq "cql0")
		{
			$caqlh->{queries}->{$qry}->{cql} = "cql"
				if ($lin =~ m/cql\(/);
		}

		if (exists($caqlh->{queries}->{$qry}->{files}->{$filnam}))
		{
			$caqlh->{queries}->{$qry}->{files}->{$filnam} += 1;
		}
		else
		{
			$caqlh->{queries}->{$qry}->{files}->{$filnam} = 1;
		}

		# extrapolate the "basic query" contained within the specified query
		my $basic_qry = lc($qry);

		$basic_qry =~ s/\s+/ /g;

		# trim leading and trailing spaces
		$basic_qry =~ s/^\s*//;
		$basic_qry =~ s/\s*$//;

		# tag the query as delete, count(*), for update
		$caqlh->{queries}->{$qry}->{bDelete} = 
			($basic_qry =~ m/^delete/) ? 1 : 0;
		$caqlh->{queries}->{$qry}->{bCount}  = 
			($caqlh->{queries}->{$qry}->{bDelete}) ? 1 : 
			(($basic_qry =~ m/count\(\*\)/) ? 1 : 0);
		$caqlh->{queries}->{$qry}->{bUpdate} = 
			($basic_qry =~ m/for update/) ? 1 : 0;
		$caqlh->{queries}->{$qry}->{bInsert} = 
			($basic_qry =~ m/^insert/) ? 1 : 0;

		die "bad query in $filnam: $qry -- cannot DELETE ... FOR UPDATE"
			if ($caqlh->{queries}->{$qry}->{bDelete} &&
				$caqlh->{queries}->{$qry}->{bUpdate});

		die "bad query in $filnam: $qry -- cannot INSERT ... FOR UPDATE"
			if ($caqlh->{queries}->{$qry}->{bInsert} &&
				$caqlh->{queries}->{$qry}->{bUpdate});

		# strip out delete, count(*), for update attributes
		$basic_qry =~ s/^select\s+count\(\*\)/select */;

		$basic_qry =~ s/^delete/select */;

		# temporarily treat INSERT as SELECT * (but reverse later)
		$basic_qry =~ s/^insert\s+into/select * from/;

		# SELECT ... ORDER BY ... FOR UPDATE 

		$basic_qry =~ s/\s*for update\s*$//;

		if ($basic_qry =~ m/order\s+by/)
		{
			my @oby = ($basic_qry =~ m/order\s+by\s+(.*)/);

			die "bad query in $filnam: $qry -- invalid ORDER BY"
				unless (scalar(@oby));

			$caqlh->{queries}->{$qry}->{oby} = { rawtxt => $oby[0] };
			$basic_qry =~ s/\s*order\s+by\s*.*$//;
		}

		# check for a column name in SELECT list
		# NOTE: only support a single column currently
		$caqlh->{queries}->{$qry}->{colnum} = "InvalidAttrNumber";

		if ($basic_qry =~ m/^select\s+(.*)\s+from/i)
		{
			my $tname;

			if ($basic_qry =~ m/^select\s+\*\s+from/i)
			{
				my @ccc = 
					($basic_qry =~ m/^select\s+\*\s+from\s+(\w*)\s*/i);
				$tname	= shift @ccc;

				$caqlh->{queries}->{$qry}->{tablename} = $tname;
			}
			else
			{
				my @ccc = 
					($basic_qry =~ m/^select\s+(\w*)\s+from\s+(\w*)\s+/i);

				my $colname = shift @ccc;
				$tname	= shift @ccc;
				
				$caqlh->{queries}->{$qry}->{colname} = $colname;
				$caqlh->{queries}->{$qry}->{tablename} = $tname;

				die "no type for $colname! : $filnam: $qry"
					unless (exists($bigh->{$tname}->{colh}->{$colname}));
				
				$caqlh->{queries}->{$qry}->{colnum} =
					anum_key($tname, $colname);
				
				# oid column is system column (usually)
				
				if (($colname eq 'oid') && 
					(exists($bigh->{$tname}->{with}->{oid})) &&
					($bigh->{$tname}->{with}->{oid}))
				{
					$caqlh->{queries}->{$qry}->{colnum} =
						'ObjectIdAttributeNumber';
				}

				# remove the column name
				$basic_qry =~ s/^select\s+.*\s+from/select \* from/;
			}

			# process ORDER BY cols (if any)
			if (exists($caqlh->{queries}->{$qry}->{oby}))
			{
				my $rawoby = $caqlh->{queries}->{$qry}->{oby}->{rawtxt};

				$rawoby =~ s/^\s+//;
				$rawoby =~ s/\s+$//;
				
				my @obylist;

				if ($rawoby !~ m/\,/)
				{
					push @obylist, $rawoby;
				}
				else
				{
					@obylist = split(/\s*\,\s*/, $rawoby);
				}
				die "bad query in $filnam: $qry -- invalid ORDER BY"
					unless (scalar(@obylist));

				# get ORDER BY column names and attribute numbers
				my $obnams = []; # list of names
				my $obnums = []; # list of attribute numbers
				my $obpred = []; # predicate list for choose_index()

				for my $obyitem (@obylist)
				{
					$obyitem =~ s/^\s+//;
					$obyitem =~ s/\s+$//;

					die "no type for ORDER BY $obyitem! : $filnam: $qry"
						unless (exists($bigh->{$tname}->{colh}->{$obyitem}));

					push @{$obnams}, $obyitem;
					push @{$obpred}, {key =>$ obyitem};
					push @{$obnums}, anum_key($tname, $obyitem);
				}
				$caqlh->{queries}->{$qry}->{oby}->{colnames} = $obnams;
				$caqlh->{queries}->{$qry}->{oby}->{attnums}  = $obnums;

				# choose an _exact_ match index (columns match ORDER BY)
				my $obyidx = choose_index($bigh, $tname, $obpred, 1);

				die "no index for ORDER BY! : $filnam: $qry"
					unless (defined($obyidx));
				
				$caqlh->{queries}->{$qry}->{oby}->{index} = $obyidx;

				# NOTE: Add ORDER BY to distinguish basic query with
				# required order from same basic query with optional
				# order.

				$basic_qry .= " ORDER_BY " . $obyidx->{CamelCaseIndexId};

			}
		} # end if select
		die "bad query in $filnam: $qry"
			unless ($basic_qry =~ m/^select\s+\*\s+from/i);

		# fix the basic query to show INSERT
		$basic_qry =~ s/^select\s+\*\s+from/insert into/i
			if ($caqlh->{queries}->{$qry}->{bInsert});

		$caqlh->{queries}->{$qry}->{basic} = $basic_qry;

		$caqlh->{basic}->{$basic_qry} = { files => {} , 
										  cql => "cql0",
										  indexes => {}, 
										  num_ins_ops => 0,
										  num_upd_ops => 0,
										  num_del_ops => 0 }
		unless (exists($caqlh->{basic}->{$basic_qry}));

		# track insert/update/delete operations
		$caqlh->{basic}->{$basic_qry}->{num_ins_ops} += 1
			if ($caqlh->{queries}->{$qry}->{bInsert});

		$caqlh->{basic}->{$basic_qry}->{num_upd_ops} += 1
			if ($caqlh->{queries}->{$qry}->{bUpdate});

		$caqlh->{basic}->{$basic_qry}->{num_del_ops} += 1
			if ($caqlh->{queries}->{$qry}->{bDelete});

		# detect case of pure cql0 queries
		if ($caqlh->{basic}->{$basic_qry}->{cql} eq "cql0")
		{
			$caqlh->{basic}->{$basic_qry}->{cql} = "cql"
				if ($caqlh->{queries}->{$qry}->{cql} eq "cql")
		}

		# track file references for this query
		if (exists($caqlh->{basic}->{$basic_qry}->{files}->{$filnam}))
		{
			$caqlh->{basic}->{$basic_qry}->{files}->{$filnam} += 1;
		}
		else
		{
			$caqlh->{basic}->{$basic_qry}->{files}->{$filnam} = 1;
		}


	} # end for my lin

} # end get_caql

sub gperf_header
{
    my $bigstr = <<'EOF_bigstr';
%{
{GENERAL_HDR}
%}
struct caql_hash_cookie
{
	const char *name;       /* caql string */
	int uniqquery_code;     /* corresponding unique query */
	int basequery_code;     /* corresponding base query */
	int bDelete;            /* query performs DELETE */
	int bCount;             /* SELECT COUNT(*) (or DELETE) */
	int bUpdate;            /* SELECT ... FOR UPDATE */
	int bInsert;            /* INSERT INTO  */
	AttrNumber attnum;      /* column number (or 0 if no column specified) */
};
%%
EOF_bigstr
	return $bigstr;
}

sub more_header
{
    my $bigstr = <<'EOF_bigstr';
/*-------------------------------------------------------------------------
 *
 * catquery.c
 *	  general catalog table access methods (internal api)
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <math.h>
#include <fcntl.h>
#include <locale.h>
#include <string.h>
#include <unistd.h>

#include "access/genam.h"
#include "access/heapam.h"
#include "access/relscan.h"
#include "access/transam.h"

#include "catalog/caqlparse.h"
#include "catalog/catalog.h"
#include "catalog/catquery.h"
#include "catalog/indexing.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_appendonly_alter_column.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_autovacuum.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_class.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_conversion.h"
#include "catalog/pg_database.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_description.h"
#include "catalog/pg_extprotocol.h"
#include "catalog/pg_exttable.h"
#include "catalog/pg_filespace.h"
#include "catalog/pg_filespace_entry.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_language.h"
#include "catalog/pg_largeobject.h"
#include "catalog/pg_listener.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_partition.h"
#include "catalog/pg_partition_rule.h"
#include "catalog/pg_pltemplate.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_resqueue.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shdepend.h"
#include "catalog/pg_shdescription.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_user_mapping.h"
#include "catalog/pg_window.h"
#include "catalog/pg_tidycat.h"

#include "catalog/gp_configuration.h"
#include "catalog/skylon_vlabel.h"
#include "catalog/skylon_elabel.h"
#include "catalog/skylon_vlabel_attribute.h"
#include "catalog/skylon_elabel_attribute.h"
#include "catalog/skylon_graph_vlabel.h"
#include "catalog/skylon_graph_elabel.h"
#include "catalog/skylon_graph.h"
#include "catalog/skylon_index.h"
#include "catalog/gp_segment_config.h"
#include "catalog/gp_san_config.h"

#include "catalog/gp_master_mirroring.h"
#include "catalog/gp_persistent.h"
#include "catalog/gp_global_sequence.h"
#include "catalog/gp_version.h"
#include "catalog/toasting.h"
#include "catalog/gp_policy.h"

#include "miscadmin.h"
#include "storage/fd.h"
#include "utils/fmgroids.h"
#include "utils/relcache.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"

#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/inval.h"

#include "cdb/cdbpersistenttablespace.h"
#include "cdb/cdbvars.h"

{STATIC_CAQL_LOCKWELL}

{CAQL_LOG_HASH}

/* ----------------------------------------------------------------
 * cq_lookup()
 * cq_lookup() defines a hash cookie for every cql() declaration.  The
 * cookie associates the caql string with a "base query" function
 * [caql_basic_fn_#()] that constructs the scan for the query.
 * caql_switch() dispatches on the cookie to the base query function.
 * ----------------------------------------------------------------
*/

EOF_bigstr
	return $bigstr;
} # end more_header


sub caql_switch
{
    my $bigstr = <<'EOF_bigstr';

/* ----------------------------------------------------------------
 * caql_switch()
 * Given a cookie, dispatch to the appropriate "base query" function to
 * construct the scan, and return a cqContext
 * NOTE: the caql_switch frees the cql after it sets up the pCtx
 * ----------------------------------------------------------------
 */

static
cqContext *caql_switch(struct caql_hash_cookie *pchn, 
					   cqContext *pCtx,
					   cq_list *pcql)

{

	Assert(pCtx); /* must have a valid context */

	/* set the snapshot and lockmodes */
	if (!pCtx->cq_setsnapshot)
		pCtx->cq_snapshot = SnapshotNow;

	if (!pCtx->cq_setlockmode)
	{
		if (pchn->bDelete || pchn->bUpdate || pchn->bInsert)
			pCtx->cq_lockmode = RowExclusiveLock;
		else
			pCtx->cq_lockmode = AccessShareLock;
	}

	/* XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX */
	if (1) /* implicit locking everywhere */
	{
		pCtx->cq_setpklock = 1;
		pCtx->cq_pklock_excl = 
			(pchn->bDelete || pchn->bUpdate || pchn->bInsert);
	}
	/* XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX */

	/* pcql must be valid */
	Assert(pcql && pcql->bGood);

	/* get everything we need from cql */
	for (int ii = 0; ii < pcql->maxkeys; ii++)
	{
		pCtx->cq_datumKeys[ii] = pcql->cqlKeys[ii];
	}
	pCtx->cq_NumKeys = pcql->maxkeys;
	pCtx->cq_cacheKeys = &pCtx->cq_datumKeys[0];

	pCtx->cq_uniqquery_code = pchn->uniqquery_code;
	pCtx->cq_basequery_code = pchn->basequery_code;

	/* NOTE: pass the cql to basic functions -- optionally 
	 * used for debugging 
	 */
	switch(pchn->basequery_code)
	{
		{ALLCASES}
	  default:
		break;
		/* bad */
	}

	/* NOTE: free up the cql before we return */
	pcql->bGood = false;
	pfree(pcql);

	return (pCtx);
} /* end caql_switch */
EOF_bigstr
	return $bigstr;
}

sub caql_switch_case
{
    my $bigstr = <<'EOF_bigstr';
case {CASEVAL}: /* {CASECOMM} */
	pCtx->cq_sysScan = {FUNCNAME}(pCtx, pcql, {IS_LOCKENTIRETABLE});
	break;
EOF_bigstr
	return $bigstr;
}

sub caql_iud_switch
{
    my $bigstr = <<'EOF_bigstr';

/* ----------------------------------------------------------------
 * caql_iud_switch()
 * dispatch to the appropriate "insert/update/delete" function 
 * for concurrent update validation
 * ----------------------------------------------------------------
 */
static
void caql_iud_switch(cqContext *pCtx, int is_iud,
					 HeapTuple oldtup, HeapTuple newtup, bool dontWait)
{
	LOCKMODE pklockmode = AccessExclusiveLock;

	Assert(pCtx); /* must have a valid context */

	if (pCtx->cq_setpklock)
	{
		/* WAIT if caql_PKLOCK() is set */
		dontWait = false;
		if (!pCtx->cq_pklock_excl)
			pklockmode = AccessShareLock;
	}

	switch(pCtx->cq_basequery_code)
	{
		{ALL_IUD_CASES}
	  default:
		break;
		/* bad */
	}

} /* end caql_iud_switch */
EOF_bigstr
	return $bigstr;
} # end caql_iud_switch

sub caql_iud_switch_case
{
    my $bigstr = <<'EOF_bigstr';
case {CASEVAL}: /* {CASECOMM} */
	{IUD_FUNC_NAME}(pCtx, is_iud, oldtup, newtup, dontWait, pklockmode);
	break;
EOF_bigstr
	return $bigstr;
} 

sub caql_iud_delete
{
    my $bigstr = <<'EOF_bigstr';
/* 
  if deleting, {IUD_TNAME} primary key may be referenced in:
  {IUD_PKTABS} 
*/
EOF_bigstr
	return $bigstr;
} 

sub caql_iud_insertupdate
{
    my $bigstr = <<'EOF_bigstr';
/* 
  if insert/update, check foreign keys against:	
  {IUD_FKTABS} 
*/
EOF_bigstr
	return $bigstr;
} 

sub caql_makehash
{
    my $bigstr = <<'EOF_bigstr';
d = caql_getattr_internal(pCtx, {TUPLE}, {MHATTNUM},  &isnull);
{HASHVAR} = caql_pkhash(pCtx, {HASHVAR}, d, isnull, {MHTYPOID});

EOF_bigstr
	return $bigstr;
} # end caql_makehash

sub static_caql_log_hash_create
{
    my $bigstr = <<'EOF_bigstr';
if (gp_enable_caql_logging && CaQLLogHash == NULL)
{
	HASHCTL hash_ctl;
	hash_ctl.keysize = sizeof(CaQLLogTag);
	hash_ctl.entrysize = sizeof(CaQLLogTag);
	hash_ctl.hash = tag_hash;

	CaQLLogHash = hash_create("caql log hash",
					1000,
					&hash_ctl,
					HASH_ELEM | HASH_FUNCTION);
}
EOF_bigstr
	return $bigstr;
}

sub static_caql_log_hash
{
    my $bigstr = <<'EOF_bigstr';
static HTAB *CaQLLogHash = NULL;

typedef struct CaQLLogTag
{
	char filename[MAXPGPATH];
	int lineno;
} CaQLLogTag;

typedef struct CaQLLogEntry
{
	CaQLLogTag key;
} CaQLLogEntry;

static uint32 caqlLogHashCode(CaQLLogTag *tagPtr)
{
	return get_hash_value(CaQLLogHash, (void *)tagPtr);
}

EOF_bigstr
	return $bigstr;
} # end static_caql_log_hash

sub static_caql_lockwell
{
    my $bigstr = <<'EOF_bigstr';
static void caql_lockwell(cqContext		*pCtx, 
						  Oid			 relid, 
						  LOCKMODE		 lockmode,
						  HeapTuple		 tup,
						  char			*tablenbame,
						  char			*colstr,
						  Oid			 indexid,
						  Oid			 hashoid,
						  bool			 dontWait,
						  bool			 ignoreInvalidTuple
						  
);

static Oid caql_pkhash(cqContext		*pCtx, 
					   Oid				 hashoid, 
					   Datum			 d,
					   bool				 isNull,
					   Oid				 typoid 
);

static void caql_lock_entiretable(cqContext		*pCtx, 
								  Oid			 relid, 
								  LOCKMODE		 lockmode,
								  bool			 dontWait
);

EOF_bigstr
	return $bigstr;
} # end static_caql_lockwell

sub caql_heapclose_releaselock
{
    my $bigstr = <<'EOF_bigstr';
heap_close((pCtx)->cq_heap_rel, (pCtx)->cq_lockmode); \
EOF_bigstr
} # end caql_heapclose_releaselock

# if not in a transaction, release the table lock, else hold it
# (until the txn commits/aborts)
sub caql_heapclose_holdlock
{
    my $bigstr = <<'EOF_bigstr';
{ if (!IsTransactionState()) \
heap_close((pCtx)->cq_heap_rel, (pCtx)->cq_lockmode); \
else heap_close((pCtx)->cq_heap_rel, NoLock); } \
EOF_bigstr
} # end caql_heapclose_holdlock

# build the C code for the caql fetch functions (getcount(), getfirst(), etc)
sub caql_lockwell_func_body
{
    my $bigstr = <<'EOF_bigstr';

/* ----------------------------------------------------------------
 * caql_lockwell()
 * 
 * acquire a primary key lock on the index
 * ----------------------------------------------------------------
 */
static void caql_lockwell(cqContext		*pCtx, 
						  Oid			 relid, 
						  LOCKMODE		 lockmode,
						  HeapTuple		 tup,
						  char			*tablenbame,
						  char			*colstr,
						  Oid			 indexid,
						  Oid			 hashoid,
						  bool			 dontWait,
						  bool			 ignoreInvalidTuple
)
{
//	Relation			rel;
	LOCKTAG				tag;
	LockAcquireResult	res;
	Oid					dbid;

//	Assert(RelationIsValid(pCtx->cq_heap_rel));
//	rel  = pCtx->cq_heap_rel;

//	Assert (!pCtx->cq_usesyscache); /* ok to have syscache as long as real rel? */

	/* XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	 * XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	 * XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	 * Disable locking on segments...
	 * XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	 * XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
	 */
	if (Gp_role != GP_ROLE_DISPATCH)
		return;

	if (!criticalRelcachesBuilt || !IsTransactionState())
		return;

	if (IsSharedRelation(relid))
		dbid = InvalidOid;
	else
		dbid = MyDatabaseId;

	if (!ignoreInvalidTuple && !HeapTupleIsValid(tup))
		return;

//	SET_LOCKTAG_RELATION(tag, dbid, relid);
	/* use gp_san_configuration for now */

	Oid keyoid = hashoid;

	/* XXX XXX: need a check to prevent lock of invalid oid for
	   foreign keys (but should be legal for case of bool false=0 mapped
	   into oid )
	 */
	if (!OidIsValid(keyoid))
		return;

	 tag.locktag_field3 = keyoid;  /* 32 bit for Oid */
	 tag.locktag_field4 = indexid; /* 16 bit for indexid (less than 64k) */

	 /* get a lock (and don't wait) */
    res = LockAcquire(&tag, lockmode, false, dontWait);

	if (LOCKACQUIRE_NOT_AVAIL == res)
	{
		ereport(ERROR,
				(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
				 errmsg("concurrent operation on resource: %s(%s)", 
				 tablenbame, colstr)));
	}

}


/* ----------------------------------------------------------------
 * caql_pkhash()
 * 
 * hash a datum by type to a 32 bit value suitable for a 
 * primary key lock
 * ----------------------------------------------------------------
 */

/* 
  hash built out of a multiplier (called iteratively over the bytes
  of the key) and a final shift to fix up low order bits 
*/
#define pkhashmult(h, k)   ( ((h)*33) + (k) )
#define pkhashshift(h)    ( (h) + ((h) >> 5) )

static Oid caql_pkhashloop(Oid	 hashoid, 
						   char *p,
						   int	 len
)
{
	for (int ii = 0; ii < len; ii++)
	{
		hashoid = pkhashmult(hashoid, *p);
		p++;
	}
	hashoid = pkhashshift(hashoid);
	return hashoid;
}

static Oid caql_pkhash(cqContext		*pCtx, 
					   Oid				 hashoid, 
					   Datum			 d,
					   bool				 isNull,
					   Oid				 typoid 
)
{
	if (isNull)
		return hashoid;

	if ((0 == hashoid) && (OIDOID == typoid))
		return (DatumGetObjectId(d));

	switch(typoid)
	{
		case OIDOID:
			hashoid = pkhashmult(hashoid, DatumGetObjectId(d));
			hashoid = pkhashshift(hashoid);
			break;
		case CHAROID:
			if (0 == hashoid)
			{
				hashoid = DatumGetChar(d);
			}
			else
			{		
				hashoid = pkhashmult(hashoid, DatumGetChar(d));
				hashoid = pkhashshift(hashoid);
			}
			break;
		case BOOLOID:
			if (0 == hashoid)
			{
				hashoid = DatumGetBool(d);
			}
			else
			{		
				hashoid = pkhashmult(hashoid, DatumGetBool(d));
				hashoid = pkhashshift(hashoid);
			}
			break;
		case INT2OID:
			if (0 == hashoid)
			{
				hashoid = DatumGetInt16(d);
			}
			else
			{		
				hashoid = pkhashmult(hashoid, DatumGetInt16(d));
				hashoid = pkhashshift(hashoid);
			}
			break;
		case INT4OID:
			if (0 == hashoid)
			{
				hashoid = DatumGetInt32(d);
			}
			else
			{		
				hashoid = pkhashmult(hashoid, DatumGetInt32(d));
				hashoid = pkhashshift(hashoid);
			}
			break;
		case INT8OID:
		{
			int64 result = DatumGetInt64(d);

			hashoid = caql_pkhashloop(hashoid, (char *)&result, 8);
			break;		
		}
		case NAMEOID:
		{
			char *result = NameStr(*(DatumGetName(d)));

			hashoid = caql_pkhashloop(hashoid, result, strlen(result));
			break;		
		}
		case OIDVECTOROID:
		{
			/* Note: see cdbhash.c */
			oidvector  *oidvec_buf = (oidvector *) DatumGetPointer(d);
			hashoid = caql_pkhashloop(hashoid, 
									  (char *)(oidvec_buf->values),
									  (oidvec_buf->dim1 * sizeof(Oid)));
			break;		
		}
		default:
			break; /* bad */
	}

	
	return hashoid;
}

/* ----------------------------------------------------------------
 * caql_lock_entiretable()
 * 
 * lock the whole catalog table if no primary key
 * (using the pg_class(oid) primary key lock)
 * ----------------------------------------------------------------
 */

static void caql_lock_entiretable(cqContext		*pCtx, 
								  Oid			 relid, 
								  LOCKMODE		 lockmode,
								  bool			 dontWait
)
{
	Oid	newhash	= 0;
	
	newhash = caql_pkhash(pCtx, newhash, ObjectIdGetDatum(relid),
						  false /* isnull */, OIDOID);
	
	caql_lockwell(pCtx, RelationRelationId,
				  lockmode, NULL,
				  "pg_class",
				  "oid",
				  ClassOidIndexId,
				  newhash,
				  dontWait,
				  true /* ignore invalid tuple */
		);

	AcceptInvalidationMessages();  /* syscache could be out of date after lock wait */
}

EOF_bigstr
	return $bigstr;
} # end caql_lockwell

sub caql_iud_lockwell
{
    my $bigstr = <<'EOF_bigstr';
if (HeapTupleIsValid({TUPLE}))
{
	Datum		d;
	bool		isnull;

	{LOCKWELL_HASHKEY2} = 0;

	{LOCKWELL_MAKEHASH}
	caql_lockwell(pCtx, {TABLERELID}, 
				  {LOCKMODE}, {TUPLE},
				  "{LW_TNAME}",
				  "{COLSTR}",
				  {LOCKWELL_IDX},
				  {LOCKWELL_HASHKEY},
				  dontWait,
				  false /* don't ignore invalid tuple */
		);
}
EOF_bigstr
	return $bigstr;
} 

sub caql_iud_lockwell_oidvector
{
    my $bigstr = <<'EOF_bigstr';
if (HeapTupleIsValid({TUPLE}))
{
	Datum		d;
	bool		isnull;

	/* NOTE: special case of oid{LW_VEC_OR_ARRAY} */
	{LOCKWELL_MAKEHASH}
	ArrayType *oidarr = isnull ? NULL : 
						(ArrayType *) {LW_GETDATUM}(d);

	/* must be a 1 dimensional array of oid 
	  (either oidvector or oid array) 
	  */
	if (!isnull && oidarr)
	{
		Assert((ARR_NDIM(oidarr) == 1) &&
			   (ARR_ELEMTYPE(oidarr) == OIDOID));

		Oid *ovp = (Oid *)ARR_DATA_PTR(oidarr);

		/* lock every oid in the array */
		for (int ii=0; ii < ARR_DIMS(oidarr)[0]; ii++)
		{
			{LOCKWELL_HASHKEY2} = ovp[ii];

			caql_lockwell(pCtx, {TABLERELID}, 
						  {LOCKMODE}, {TUPLE},
						  "{LW_TNAME}",
						  "{COLSTR}",
						  {LOCKWELL_IDX},
						  {LOCKWELL_HASHKEY},
						  dontWait,
						  false /* don't ignore invalid tuple */
						);
		}
	}
}
EOF_bigstr
	return $bigstr;
} 

sub caql_iud_buildhash
{
    my $bigstr = <<'EOF_bigstr';
if (HeapTupleIsValid({IUD_BH_TUP}))
{
	if (!pCtx->cq_setpklock)
		Assert(RelationIsValid(pCtx->cq_heap_rel));

	{IUD_BH_VAL} = 0;
}
EOF_bigstr
	return $bigstr;
}

sub caql_iud_function
{
    my $bigstr = <<'EOF_bigstr';

static
void {IUD_FUNC_NAME}(cqContext *pCtx, int is_iud, 
					 HeapTuple oldtup, HeapTuple newtup, bool dontWait,
					 LOCKMODE pklockmode)
{
	Oid oldhash = 0;
	Oid newhash = 0;

	{IUD_BUILDHASH}
	{IUD_IDX}
	{IUD_IDX_XLOCKWELL}

	/* NOTE: don't get fk locks if only needed share locks on pk */
	if (pklockmode != AccessExclusiveLock)
		return;

	if (!is_iud)
	{
		{IUD_DELETE}
	}
	else
	{
		dontWait = true; /* never wait for share locks on foreign keys */
		{IUD_INSERTUPDATE}
		{IUD_SHLOCKWELL}
	}
} /* end {IUD_FUNC_NAME} */
EOF_bigstr
	return $bigstr;
} 


# build the C code for the caql fetch functions (getcount(), getfirst(), etc)
sub caql_fetch_funcs
{
    my $bigstr = <<'EOF_bigstr';

{CAQL_LOCKWELL}
{BUILTIN_OBJECT_CHECK}
{DISABLE_CATALOG_CHECK}
{DISABLE_ATTRIBUTE_CHECK}

EOF_bigstr
	return $bigstr;
} # end caql_fetch_funcs

# Project NameData Column Case Statement
sub projnamcolcase
{
	my $bFixed = shift;

	my $bigstr;

	if ($bFixed)
	{
	$bigstr = <<'EOF_bigstr';

case {PROJCOLATTNUM}: /* {PROJCOLUMNNAME} */
	result = pstrdup(
		NameStr((({PROJTABLEFORM}) 
				 GETSTRUCT(tuple))->{PROJCOLUMNNAME}));
	if (pbIsNull) *pbIsNull = false;
	break;
EOF_bigstr
	}
	else
	{
	$bigstr = <<'EOF_bigstr2';

case {PROJCOLATTNUM}: /* {PROJCOLUMNNAME} */
{
	bool		isnull;
	Datum		d = 
		caql_getattr_internal(pCtx, tuple, {PROJCOLATTNUM}, &isnull);

	if (!isnull)
		result = pstrdup(
			NameStr(*(DatumGetName(d))));

	if (pbIsNull) *pbIsNull = isnull;
}
	break;
EOF_bigstr2
	}

	return $bigstr;
}

# Project Oid Column Case Statement
sub projoidcolcase
{
	my $bFixed = shift;

    my $bigstr;

	if ($bFixed)
	{
		$bigstr = <<'EOF_bigstr';

case {PROJCOLATTNUM}: /* {PROJCOLUMNNAME} */
	result = (Oid) 
		(({PROJTABLEFORM}) 
		 GETSTRUCT(tuple))->{PROJCOLUMNNAME};
	if (pbIsNull) *pbIsNull = false;
	break;
EOF_bigstr
	}
	else
	{
		$bigstr = <<'EOF_bigstr2';

case {PROJCOLATTNUM}: /* {PROJCOLUMNNAME} */
{
	bool		isnull;
	Datum		d = 
		caql_getattr_internal(pCtx, tuple, {PROJCOLATTNUM}, &isnull);

	if (!isnull)
		result = DatumGetObjectId(d);

	if (pbIsNull) *pbIsNull = isnull;
}
	break;
EOF_bigstr2
	}
	return $bigstr;
}

# Project Text Column Case Statement
sub projtxtcolcase
{
	# NOTE: treat all text columns as variable (Nullable)

    my $bigstr = <<'EOF_bigstr';

case {PROJCOLATTNUM}: /* {PROJCOLUMNNAME} */
{
	bool		isnull;
	Datum		d = 
		caql_getattr_internal(pCtx, tuple, {PROJCOLATTNUM}, &isnull);

	if (!isnull)
		result = DatumGetCString(
			DirectFunctionCall1(textout, d));

	if (pbIsNull) *pbIsNull = isnull;
}
	break;
EOF_bigstr
	return $bigstr;
}

sub projcase_switch
{
    my $bigstr = <<'EOF_bigstr';

case {CASERELID}: /* {PROJTABLENAME} */
{
	switch({PROJATTNUM})
	{
		{PROJCOLCASE}
	  default:
		elog(ERROR, "column not {PROJTYPMSG}: %s\nfile: %s, line %d", 
			 caql_str, filenam, lineno);
	}
} /* end {PROJTABLENAME} */
	break;
EOF_bigstr
	return $bigstr;
}

sub cstring_table_switch
{
    my $bigstr = <<'EOF_bigstr';

switch({TABLERELID})
{
	{PROJCASE}
  default:
	elog(ERROR, "could not get column for relation: %s\nfile: %s, line %d", 
		 caql_str, filenam, lineno);
}
EOF_bigstr
	return $bigstr;
}

# build the C code for the caql fetch functions (getcstring() )
sub caql_fetch_cols
{
    my $bigstr = <<'EOF_bigstr';

/* XXX XXX: temp fix for gp_distro reference in getoid_plus */
typedef FormData_gp_policy *Form_gp_distribution_policy;

/* ----------------------------------------------------------------
 * caql_getoid_plus()
 * Return an oid column from the first tuple and end the scan.
 * Note: this works for regproc columns as well, but you should cast
 * the output as RegProcedure.
 * ----------------------------------------------------------------
 */
Oid caql_getoid_plus(cqContext *pCtx0, int *pFetchcount,
					 bool *pbIsNull, cq_list *pcql)
{
	const char*				 caql_str = pcql->caqlStr;
	const char*				 filenam  = pcql->filename;
	int						 lineno	  = pcql->lineno;
	struct caql_hash_cookie	*pchn	  = {LOOKUP}(caql_str, strlen(caql_str), pcql);
	cqContext				*pCtx;
	cqContext				 cqc;
	HeapTuple				 tuple;
	Relation				 rel;
	Oid						 result	  = InvalidOid;

	if (NULL == pchn)
		elog(ERROR, "invalid caql string: %s\nfile: %s, line %d", 
			 caql_str, filenam, lineno);

	Assert(!pchn->bInsert); /* INSERT not allowed */

	/* use the provided context, or provide a clean local ctx  */
	if (pCtx0)
		pCtx = pCtx0;
	else
		pCtx = cqclr(&cqc);

	pCtx = caql_switch(pchn, pCtx, pcql);
	/* NOTE: caql_switch frees the pcql */
	rel  = pCtx->cq_heap_rel;

	if (pFetchcount) *pFetchcount = 0;
	if (pbIsNull) *pbIsNull = true;

	/* use the SysCache */
	if (pCtx->cq_usesyscache)
	{
		tuple = SearchSysCacheKeyArray(pCtx->cq_cacheId, 
									   pCtx->cq_NumKeys, 
									   pCtx->cq_cacheKeys);
	}
	else
	{
		tuple = systable_getnext(pCtx->cq_sysScan);
	}

	disable_catalog_check(pCtx, tuple);
	if (HeapTupleIsValid(tuple))
	{
		if (pFetchcount) *pFetchcount = 1;
		
		/* if attnum not set, (InvalidAttrNumber == 0)
		 * use tuple oid, else extract oid from column 
		 * (includes ObjectIdAttributeNumber == -2) 
		 */
		if (pchn->attnum <= InvalidAttrNumber) 
		{
			if (pbIsNull) *pbIsNull = false;
			result = HeapTupleGetOid(tuple);
		}
		else /* find oid column */
		{
			{GETOIDTABLESWITCH}
		}
	} /* end HeapTupleIsValid */

	if (pCtx->cq_usesyscache)
	{  
		if (HeapTupleIsValid(tuple))
			ReleaseSysCache(tuple);
	}
	else
	{	
		if (pFetchcount && HeapTupleIsValid(tuple))
		{				
			if (HeapTupleIsValid(systable_getnext(pCtx->cq_sysScan)))
			{
				*pFetchcount = 2;	
			}
		}
		systable_endscan(pCtx->cq_sysScan); 
	}		
	caql_heapclose(pCtx);

	return (result);
} /* end caql_getoid_plus */

/* ----------------------------------------------------------------
 * caql_getoid_only()
 * Return the oid of the first tuple and end the scan
 * If pbOnly is not NULL, return TRUE if a second tuple is not found,
 * else return FALSE
 * ----------------------------------------------------------------
 */
Oid caql_getoid_only(cqContext *pCtx0, bool *pbOnly, cq_list *pcql)
{
	const char*				 caql_str = pcql->caqlStr;
	const char*				 filenam  = pcql->filename;
	int						 lineno	  = pcql->lineno;
	struct caql_hash_cookie	*pchn	  = {LOOKUP}(caql_str, strlen(caql_str), pcql);
	cqContext				*pCtx;
	cqContext				 cqc;
	HeapTuple				 tuple;
	Relation				 rel;
	Oid						 result = InvalidOid;

	if (NULL == pchn)
		elog(ERROR, "invalid caql string: %s\nfile: %s, line %d", 
			 caql_str, filenam, lineno);

	Assert(!pchn->bInsert); /* INSERT not allowed */

	/* use the provided context, or provide a clean local ctx  */
	if (pCtx0)
		pCtx = pCtx0;
	else
		pCtx = cqclr(&cqc);

	pCtx = caql_switch(pchn, pCtx, pcql);
	/* NOTE: caql_switch frees the pcql */
	rel  = pCtx->cq_heap_rel;

	if (pbOnly) *pbOnly = true;

	/* use the SysCache */
	if (pCtx->cq_usesyscache)
	{
		tuple = SearchSysCacheKeyArray(pCtx->cq_cacheId, 
									   pCtx->cq_NumKeys, 
									   pCtx->cq_cacheKeys);

		disable_catalog_check(pCtx, tuple);

		if (HeapTupleIsValid(tuple))
		{
			result = HeapTupleGetOid(tuple);
			ReleaseSysCache(tuple);
			/* only one */
		}
		caql_heapclose(pCtx);

		return (result);
	}

	if (HeapTupleIsValid(tuple = systable_getnext(pCtx->cq_sysScan)))
	{
		disable_catalog_check(pCtx, tuple);

		result = HeapTupleGetOid(tuple);

		if (pbOnly)
		{
			*pbOnly = 
				!(HeapTupleIsValid(tuple = 
								   systable_getnext(pCtx->cq_sysScan)));
		}
	}
	systable_endscan(pCtx->cq_sysScan); 
	caql_heapclose(pCtx);
	return (result);
}

/* ----------------------------------------------------------------
 * caql_getcstring_plus()
 * Return a cstring column from the first tuple and end the scan.
 * ----------------------------------------------------------------
 */
char *caql_getcstring_plus(cqContext *pCtx0, int *pFetchcount,
						   bool *pbIsNull, cq_list *pcql)
{
	const char*				 caql_str = pcql->caqlStr;
	const char*				 filenam  = pcql->filename;
	int						 lineno	  = pcql->lineno;
	struct caql_hash_cookie	*pchn	  = {LOOKUP}(caql_str, strlen(caql_str), pcql);
	cqContext				*pCtx;
	cqContext				 cqc;
	HeapTuple				 tuple;
	Relation				 rel;
	char					*result = NULL;

	if (NULL == pchn)
		elog(ERROR, "invalid caql string: %s\nfile: %s, line %d", 
			 caql_str, filenam, lineno);

	Assert(!pchn->bInsert); /* INSERT not allowed */

	/* use the provided context, or provide a clean local ctx  */
	if (pCtx0)
		pCtx = pCtx0;
	else
		pCtx = cqclr(&cqc);

	pCtx = caql_switch(pchn, pCtx, pcql);
	/* NOTE: caql_switch frees the pcql */
	rel  = pCtx->cq_heap_rel;

	if (pFetchcount) *pFetchcount = 0;
	if (pbIsNull) *pbIsNull = true;

	/* use the SysCache */
	if (pCtx->cq_usesyscache)
	{
		tuple = SearchSysCacheKeyArray(pCtx->cq_cacheId, 
									   pCtx->cq_NumKeys, 
									   pCtx->cq_cacheKeys);
	}
	else
	{
		tuple = systable_getnext(pCtx->cq_sysScan);
	}

	disable_catalog_check(pCtx, tuple);

	if (HeapTupleIsValid(tuple))
	{
		if (pFetchcount) *pFetchcount = 1;
		{GETCSTRINGTABLESWITCH}
	} /* end HeapTupleIsValid */

	if (pCtx->cq_usesyscache)
	{  
		if (HeapTupleIsValid(tuple))
			ReleaseSysCache(tuple);
	}
	else
	{	
		if (pFetchcount && HeapTupleIsValid(tuple))
		{				
			if (HeapTupleIsValid(systable_getnext(pCtx->cq_sysScan)))
			{
				*pFetchcount = 2;	
			}
		}
		systable_endscan(pCtx->cq_sysScan); 
	}		
	caql_heapclose(pCtx);

	return (result);
} /* end caql_getcstring_plus */

EOF_bigstr
	return $bigstr;
} # end caql_fetch_cols


# build the gperf input file
sub do_gperf
{
	my ($caqlh, $gpf, $bigh) = @_;

	my $gpo;

	open $gpo, "> $gpf"
		or die "cannot open $gpf: $!";

	my $verzion = "unknown";
	$verzion = $glob_glob->{_sleazy_properties}->{version}
	if (exists($glob_glob->{_sleazy_properties}) &&
		exists($glob_glob->{_sleazy_properties}->{version}));

	my $slzy_argv_str = "";
	$slzy_argv_str = 
		"\nARGV: " . $glob_glob->{_sleazy_properties}->{slzy_argv_str} . "\n"
		if (exists($glob_glob->{_sleazy_properties}->{slzy_argv_str}) &&
			length($glob_glob->{_sleazy_properties}->{slzy_argv_str}));
	

	$verzion = $0 . " version " . $verzion;
	my $nnow = localtime;
	my $gen_hdr_str = "/* \n";
	$gen_hdr_str .= "   WARNING: DO NOT MODIFY THIS FILE: \n" .
		"   Generated by " . $verzion . "\n" . 
		"   on " . $nnow . "\n" . $slzy_argv_str . 
		"\n*/\n\n";

	my $static_caql_lockwell_str = (exists($glob_glob->{lockcheck}) &&
								  $glob_glob->{lockcheck}) ?
								  static_caql_lockwell() : "";

	my $caql_heapclose_str = (exists($glob_glob->{holdtablelock}) &&
								  $glob_glob->{holdtablelock}) ?
								  caql_heapclose_holdlock() :
								  caql_heapclose_releaselock();

	my $static_caql_log_hash_str = (exists($glob_glob->{logquery_hash}) &&
								  $glob_glob->{logquery_hash}) ?
								  static_caql_log_hash() : "";

	$gen_hdr_str .= 
		doformat(more_header(),
				 {
					STATIC_CAQL_LOCKWELL => $static_caql_lockwell_str,
					CAQL_HEAPCLOSE		  => $caql_heapclose_str,
					CAQL_LOG_HASH	=> $static_caql_log_hash_str
				 }
		);

	$gen_hdr_str .= "#ifdef NOT_USED\n";

	print $gpo doformat(gperf_header(),
						{
							GENERAL_HDR => $gen_hdr_str
						}
		);

# Omit unused code.  If this giant code is needed in future,
# we can come back and re-evaluate it.
if(0)
{
	# caql hash cookie initialization
	my $uniq_code = 1; # label unique queries for caqltrack
	for my $qry (sort(keys(%{$caqlh->{queries}})))
	{
		print $gpo '"' . $qry . '"' . ", "  ;

		my $basic = $caqlh->{queries}->{$qry}->{basic};

		print $gpo $uniq_code . ", ";
		print $gpo $caqlh->{basic}->{$basic}->{func_number} . ", ";
		print $gpo $caqlh->{queries}->{$qry}->{bDelete} . ", ";
		print $gpo $caqlh->{queries}->{$qry}->{bCount} . ", ";
		print $gpo $caqlh->{queries}->{$qry}->{bUpdate} . ", ";
		print $gpo $caqlh->{queries}->{$qry}->{bInsert} . ", ";
		print $gpo $caqlh->{queries}->{$qry}->{colnum} . "\n";

		$caqlh->{queries}->{$qry}->{uniqquery_code} = $uniq_code;
		$caqlh->{queries}->{$qry}->{basequery_code} = 
			$caqlh->{basic}->{$basic}->{func_number};

		$uniq_code++;
	}

	print $gpo '%%' . "\n\n";

	# print the base query functions

	print $gpo caql_stats($caqlh, $bigh);

	for my $bq (sort(keys(%{$caqlh->{basic}})))
	{
		print $gpo "/* base query: $bq  */\n";
		if (exists($caqlh->{basic}->{$bq}->{func_index}))
		{
			my $bqi = $caqlh->{basic}->{$bq}->{func_index};
			print $gpo "/* index: $bqi  */\n";
		}
		else
		{
			print $gpo "/* index: *None*  */\n";
		}
		if (exists($caqlh->{basic}->{$bq}->{syscacheid}))
		{
			my $bqsc = $caqlh->{basic}->{$bq}->{syscacheid};
			print $gpo "/* syscacheid: $bqsc  */\n";
		}
		print $gpo "/* clients: ";
		{
			# print total client count
			my $totclient = 0;
			for my $filc (sort(keys(%{$caqlh->{basic}->{$bq}->{files}})))
			{
				$totclient += $caqlh->{basic}->{$bq}->{files}->{$filc};
			}
			print $gpo $totclient;

			print $gpo "\t\t";
			# insert/update/delete
			print $gpo "i/u/d: "; 
			print $gpo $caqlh->{basic}->{$bq}->{num_ins_ops} . "/";
			print $gpo $caqlh->{basic}->{$bq}->{num_upd_ops} . "/";
			print $gpo $caqlh->{basic}->{$bq}->{num_del_ops} ;
		}

		print $gpo " \n";
		# breakdown references per file
		for my $filc (sort(keys(%{$caqlh->{basic}->{$bq}->{files}})))
		{
			my $fn2 = $filc;
			$fn2 =~ s|^.*/src/|/src/|;
			$fn2 =~ s|^(\.\./)*||; # remove leading "../"
			print $gpo " *   $fn2: ";

			print $gpo $caqlh->{basic}->{$bq}->{files}->{$filc} . "\n";
		}
		print $gpo " */\n";
		print $gpo "/* foreign key tables: ";

		if (!exists($caqlh->{basic}->{$bq}->{foreign_key_tables}))
		{
			print $gpo "*None*\n";
		}
		else
		{
			print $gpo "\n";

			for my $fktab (sort(keys(%{$caqlh->{basic}->{$bq}->{foreign_key_tables}})))
			{
				print $gpo " *   $fktab\n";
			}
		}
		print $gpo " */\n";
		# additional notes

		# note the case of a basic function where all queries are cql0
		if ($caqlh->{basic}->{$bq}->{cql} eq "cql0")
		{
			$caqlh->{basic}->{$bq}->{func_note} .= 
				"cql0 definition only - function never called\n";
		}
		
		if (exists($caqlh->{basic}->{$bq}->{func_note}) &&
			length($caqlh->{basic}->{$bq}->{func_note}))
		{
			my $fnn = $caqlh->{basic}->{$bq}->{func_note};

			$fnn =~ s/^/ *    /gm; # prefix newlines

			print $gpo "/* Notes: \n";
			print $gpo $fnn;
			print $gpo " */\n";
		}

		print $gpo $caqlh->{basic}->{$bq}->{func}, "\n";
	}

	if (exists($glob_glob->{lockcheck}) &&
		$glob_glob->{lockcheck})
    {
		# iud functions	
		print $gpo "/* start iud functions  */\n";
		print $gpo 
			doformat(
		"/* Note: {NUMIUD} iud functions for {NUMBQ} basic queries */\n\n",
				{
					NUMIUD => scalar(keys(%{$glob_glob->{iud2bq}})),
					NUMBQ => scalar(keys(%{$caqlh->{basic}}))
				});
		for my $bq (sort(keys(%{$caqlh->{basic}})))
		{
			# only print function body if it exists (duplicates were
			# eliminated), so the list has "holes" in the numbering

			if (defined($caqlh->{basic}->{$bq}->{iud_func}))
			{
				# NOTE: we deduplicated iud functions, so print the
				# list of all basic queries associated with this
				# function
				my $bql = 
					join("\n\t", 
						 @{$glob_glob->{iud2bq}->{
							 $caqlh->{basic}->{$bq}->{iud_func_name}}});

				# reformat the WHERE clause a bit
				$bql =~ s/\s+where/\n\t\twhere/gm;

				print $gpo "/* base query: \n\t$bql\n\t*/\n";
				
				print $gpo $caqlh->{basic}->{$bq}->{iud_func}, "\n";
			}
		}
		print $gpo "/* end iud functions  */\n";

	}

	# build dispatch table
	
	my $allcase = "";
	my $all_iudcase = "";

	for my $bq (sort(keys(%{$caqlh->{basic}})))
	{
		my $lock_entire_table = "false";
		my $bq_tname		  = $caqlh->{basic}->{$bq}->{tablename};

		if (exists($bigh->{$bq_tname}->{calico}) &&
			exists($bigh->{$bq_tname}->{calico}->{lock_entire_table}) &&
			($bigh->{$bq_tname}->{calico}->{lock_entire_table}))
		{
			# NOTE WELL: if this table had any basic functions that
			# could *not* use a pkey index, then all functions that
			# use this table must get a share or exclusive lock for
			# the entire table.
			$lock_entire_table = "true";
		}

		$allcase .= doformat(caql_switch_case(),
							 {
								 FUNCNAME => 
									 $caqlh->{basic}->{$bq}->{func_name},
								 CASECOMM => $bq,
								 CASEVAL => 
									 $caqlh->{basic}->{$bq}->{func_number},
								 IS_LOCKENTIRETABLE => $lock_entire_table,
							 }
			);
		$all_iudcase .= doformat(caql_iud_switch_case(),
							 {
								 IUD_FUNC_NAME => 
									 $caqlh->{basic}->{$bq}->{iud_func_name},
								 CASECOMM => $bq,
								 CASEVAL => 
									 $caqlh->{basic}->{$bq}->{func_number}
							 }
			);
	}
	my $caqlsw = doformat(caql_switch(),
						  {
							ALLCASES => $allcase
						  }
		);
	
	my $caql_iudsw = doformat(caql_iud_switch(),
						  {
							  ALL_IUD_CASES => $all_iudcase
						  }
		);

	print $gpo $caqlsw;
} # if(0)
else
{
	# We need something.  Output dummy.
	print $gpo '"SELECT 1", 1, 1, 0, 0, 0, 0, foo' . $/;
	print $gpo '%%' . $/;
}

	my $caql_iudsw = "";

	print $gpo "#endif /* NOT_USED */\n";
	if (exists($glob_glob->{lockcheck}) &&
		$glob_glob->{lockcheck})
    {
		print $gpo $caql_iudsw;
	}
	else
    {
		print $gpo "\n\n/* NOTE: caql_iud_switch locking removed */\n";
		print $gpo "#define caql_iud_switch(pctx, isiud, oldtup, newtup, dontWait) \n";
	}


	print $gpo "\n";

	# add caql_lockwell function if locking
	my $lockwell_str = (exists($glob_glob->{lockcheck}) &&
					  $glob_glob->{lockcheck}) ? 
					  caql_lockwell_func_body() : "";

	my $builtin_object_check_case_str = get_builtin_object_check_case_str();

	my $builtin_object_check_str = doformat(get_builtin_object_check_str(),
						{
							BUILTIN_OBJECT_CHECK_CASE => $builtin_object_check_case_str
						}
		);

	my $disable_catalog_check_str = get_disable_catalog_check_str();
	my $disable_attribute_check_str = get_disable_attribute_check_str();

	print $gpo doformat(caql_fetch_funcs(),
						{
							CAQL_LOCKWELL => $lockwell_str,
							LOOKUP => "cq_lookup",
							BUILTIN_OBJECT_CHECK => $builtin_object_check_str,
							DISABLE_CATALOG_CHECK => $disable_catalog_check_str,
							DISABLE_ATTRIBUTE_CHECK => $disable_attribute_check_str
						}
		);

	my $proj_cstr_case = "";
	my $proj_oid_case = "";
	if (1)
	{
		# fixed fields
		my $ffh = getfixedfields($bigh);

		my %alltabh;

		while (my ($kk, $vv) = each(%{$glob_glob->{fil2tab}}))
		{
			for my $jj (keys(%{$vv}))
			{
				$alltabh{$jj} = 1;
			}
		}

		# build column PROJECTION switch statements
		for my $projtname (sort(keys(%{$bigh})))
		{
			next # ignore comments
				if ($projtname =~ m/^\_\_/);

			next
				unless (exists($alltabh{$projtname}));

			my $proj_cstr_colcase = "";
			my $proj_oid_colcase = "";

			# for all tables, build switch statement to 
			# extract (project) tuple columns. 
			# "fixed" columns are not nullable.  

			if (exists($ffh->{$projtname}))
			{
				for my $ffcdef (@{$ffh->{$projtname}->{cols}})
				{
					my $projcolname = $ffcdef->{colname}; 					
					next 
						unless ($ffcdef->{ctype} =~ 
								m/NameData|text|Oid|regproc/);

					if ($ffcdef->{ctype} =~ m/NameData/) 
					{
						$proj_cstr_colcase .= doformat(
							projnamcolcase($ffcdef->{fixed}),
								{
									PROJCOLUMNNAME => $projcolname,
									PROJTABLEFORM => 
										struct_form_tname($projtname),
									PROJCOLATTNUM => $ffcdef->{attnum}
											   
								}
						);
					}
					elsif ($ffcdef->{ctype} =~ m/text/)
					{
						$proj_cstr_colcase .= doformat(projtxtcolcase(),
								{
									PROJCOLUMNNAME => $projcolname,
									PROJTABLEFORM => 
										struct_form_tname($projtname),
									PROJCOLATTNUM => $ffcdef->{attnum}
											   
								}
						);
					}
					# no array of Oid, regproc is equivalent to Oid
					elsif ($ffcdef->{ctype} =~ m/regproc|Oid$/) 
					{
						$proj_oid_colcase .= doformat(
							projoidcolcase($ffcdef->{fixed}),
								{
									PROJCOLUMNNAME => $projcolname,
									PROJTABLEFORM => 
										struct_form_tname($projtname),
									PROJCOLATTNUM => $ffcdef->{attnum}
											   
								}
						);
						
					}

				}
			}

			$proj_cstr_case .= doformat(projcase_switch(),
								 {
									 PROJTABLENAME => $projtname,
									 PROJATTNUM => "pchn->attnum",
									 PROJCOLCASE => $proj_cstr_colcase,
									 PROJTYPMSG => "a cstring",
									 CASERELID => 
									 $bigh->{$projtname}->{CamelCaseRelationId}

								 }
				)
				if (length($proj_cstr_colcase));
			$proj_oid_case .= doformat(projcase_switch(),
								 {
									 PROJTABLENAME => $projtname,
									 PROJATTNUM => "pchn->attnum",
									 PROJCOLCASE => $proj_oid_colcase,
									 PROJTYPMSG => "an oid",
									 CASERELID => 
									 $bigh->{$projtname}->{CamelCaseRelationId}

								 }
				)
				if (length($proj_oid_colcase));
		}


#		print $gpo "\n/*\n";
#		print $gpo Data::Dumper->Dump([$ffh]);
#		print $gpo "\n*/\n";

	}

	my $projsw = doformat(cstring_table_switch(),
						{
							TABLERELID => "pCtx->cq_relationId",
							PROJCASE => $proj_cstr_case
						}
		);
	my $projoidsw = doformat(cstring_table_switch(),
						{
							TABLERELID => "pCtx->cq_relationId",
							PROJCASE => $proj_oid_case
						}
		);

	print $gpo doformat(caql_fetch_cols(),
						{
							LOOKUP => "cq_lookup",
							GETCSTRINGTABLESWITCH => $projsw,
							GETOIDTABLESWITCH => $projoidsw
						}
		);

	print $gpo doformat(caql_logquery(),
						{}
		);

	close $gpo;

} # end do_gperf


sub get_builtin_object_check_case_str()
{
	my $str = "";
	my @clist = ("GpPolicyRelationId, FormData_gp_policy *, localoid",
				 "AggregateRelationId, Form_pg_aggregate, aggfnoid",
				 "AccessMethodOperatorRelationId, Form_pg_amop, amopclaid",
				 "AccessMethodProcedureRelationId, Form_pg_amproc, amopclaid",
				 "AppendOnlyRelationId, Form_pg_appendonly, relid",
				 "AttrDefaultRelationId, Form_pg_attrdef, adrelid",
				 "AttributeRelationId, Form_pg_attribute, attrelid",
				 "AttributeEncodingRelationId, Form_pg_attribute_encoding, attrelid",
				 "AuthMemRelationId, Form_pg_auth_members, roleid",
				 "AuthTimeConstraintRelationId, Form_pg_auth_time_constraint, authid",
				 "DependRelationId, Form_pg_depend, objid",
				 "DescriptionRelationId, Form_pg_description, objoid",
				 "ExtTableRelationId, Form_pg_exttable, reloid",
				 "FileSpaceEntryRelationId, Form_pg_filespace_entry, fsefsoid",
				 "IndexRelationId, Form_pg_index, indexrelid",
				 "InheritsRelationId, Form_pg_inherits, inhrelid",
				 "PartitionEncodingRelationId, Form_pg_partition_encoding, parencoid",
				 "PLTemplateRelationId, Form_pg_pltemplate, tmplname",
				 "TriggerRelationId, Form_pg_trigger, tgrelid",
				 "RewriteRelationId, Form_pg_rewrite, ev_class",
				 "ProcCallbackRelationId, Form_pg_proc_callback, profnoid",
				 "SharedDependRelationId, Form_pg_shdepend, objid",
				 "SharedDescriptionRelationId, Form_pg_shdescription, objoid",
				 "StatLastOpRelationId, Form_pg_statlastop, objid",
				 "StatLastShOpRelationId, Form_pg_statlastshop, objid",
				 "StatisticRelationId, Form_pg_statistic, starelid",
				 "TypeEncodingRelationId, Form_pg_type_encoding, typid",
				 "WindowRelationId, Form_pg_window, winfnoid"
				);

	for my $lin (@clist)
	{
		my @foo = split(/, /, $lin);
		if ($foo[0] =~ /PLTemplateRelationId/)
		{
			$str .= <<'EOF_str';
case PLTemplateRelationId:
	{
		char *name_str = pstrdup(NameStr(((Form_pg_pltemplate) GETSTRUCT(tuple))->tmplname));
		if ((strcmp(name_str, "plpgsql") != 0) ||
			(strcmp(name_str, "c") != 0) ||
			(strcmp(name_str, "sql") != 0) ||
			(strcmp(name_str, "internal") != 0))
			result = InvalidOid;
		break;
	}
EOF_str
		}
		else
		{
			$str .= <<EOF_str;
case $foo[0]:
	result = (Oid) (($foo[1]) GETSTRUCT(tuple))->$foo[2];
	break;
EOF_str
		}
	}

	return $str;
}

sub get_disable_catalog_check_str()
{
	my $bigstr = <<'EOF_bigstr';
EOF_bigstr

	return $bigstr;
}


sub get_disable_attribute_check_str()
{
	my $bigstr = <<'EOF_bigstr';
EOF_bigstr

	return $bigstr;
}


sub get_builtin_object_check_str()
{
	my $bigstr = <<'EOF_bigstr';
bool
is_builtin_object(cqContext *pCtx, HeapTuple tuple)
{
	Oid result = InvalidOid;

	if (!HeapTupleIsValid(tuple))
		return true;

	if (tuple->t_data->t_infomask & HEAP_HASOID)
		result = HeapTupleGetOid(tuple);
	else
	{
		switch(pCtx->cq_relationId)
		{
			{BUILTIN_OBJECT_CHECK_CASE}
			default:
				return false;
		}
	}
	if (result > FirstNormalObjectId)
		return false;

	return true;
}
EOF_bigstr

	return $bigstr;
}

sub get_syscacheid_map
{
	my ($idxcacheidmap, $whole_sysc) = @_;

	my $prevlin;
	my @lines = split(/\n/, $whole_sysc);

	for my $lin (@lines)
	{
		if ($lin !~ m/^\s*\w+IndexId\,\s*$/)
		{
			$prevlin = $lin;
			next;
		}

		my $idx = $lin;
		$idx =~ s/\,\s*$//;
		$idx =~ s/^\s*//;

		die "bad syscache file: $prevlin\n$lin" 
			unless ($prevlin =~ m|\/\*\s*\w+\s*\*\/|);

		# comment in previous line is " /* cacheid */ "
		my @foo = ($prevlin =~ m|\/\*\s*(\w+)\s*\*\/|);

		$idxcacheidmap->{$idx} = $foo[0];

		$prevlin = $lin;
	}

#	print Data::Dumper->Dump([$idxcacheidmap]);


} # end get_syscacheid_map

sub caql_stats
{
	my ($caqlh, $bigh) = @_;

    my $bigstr = <<'EOF_bigstr';

/* ----------------------------------------------------------------
 * catquery statistics
 *	
 *	total files: {nfiles}
 *	total caql queries (including inserts): {nqueries}
 *	total unique queries: {nuniq}
 *	total basic queries: {nbasic}
 *	total insert statements: {ninsert}
 *	
 *	locking - table locks required for 
 *	{numlocktab} tables, {numlockfun} functions:
 	  {tablelocks}
 *	space and case-sensitive query duplicates:
 	  {hashdups}
 * ----------------------------------------------------------------
 */

EOF_bigstr
	my $totquerycnt = 0;
	my $totinsertcnt = 0;
	my %dupcheck;

	# for all distinct queries
	while (my ($kk, $vv) = each(%{$caqlh->{queries}}))
	{

		# check for statements that differ only by spacing or case
		my $k2 = lc($kk);
		
		$k2 =~ s/\s+/ /g;
		$k2 =~ s/^\s+//;
		$k2 =~ s/\s+$//;

		$dupcheck{$k2} = []
			unless (exists($dupcheck{$k2}));

		push @{$dupcheck{$k2}}, '"' . $kk . '"';

		# for every file that contains that query, sum the count
		while (my ($jj, $ww) = each(%{$vv->{files}}))
		{
			$totquerycnt += $ww;
		}
	}
	while (my ($kk, $vv) = each(%{$caqlh->{basic}}))
	{
		$totinsertcnt += 1
			if ($kk =~ m/^insert/i);
	}

	my $tablelocks = "";
	my $hashdups   = "";

	# if the "deduplicated" query list has duplicates, print them
	while (my ($kk, $vv) = each(%dupcheck))
	{
		next unless (scalar(@{$vv}) > 1);

		# more than 1 query for "deduped" list
		$hashdups .= join("\n", @{$vv}) . "\n";
	}

	my @tlockl;
	my %tabh;
	my $numLockTab  = 0;
	my $numLockFunc = 0;

	# find functions where had to lock entire table
	for my $bq (sort(keys(%{$caqlh->{basic}})))
	{
		my $bq_tname		  = $caqlh->{basic}->{$bq}->{tablename};

		if (exists($bigh->{$bq_tname}->{calico}) &&
			exists($bigh->{$bq_tname}->{calico}->{lock_entire_table}) &&
			($bigh->{$bq_tname}->{calico}->{lock_entire_table}))
		{
			# build list of indented function names
			$tabh{$bq_tname} = ""
				unless (exists($tabh{$bq_tname}));
			$tabh{$bq_tname} .= "\n"
				if (length($tabh{$bq_tname}));
			$tabh{$bq_tname} .= "\t\t" . $caqlh->{basic}->{$bq}->{func_name};
			$numLockFunc++;
		}
	}
	
	for my $ttname (sort(keys(%tabh)))
	{
		$numLockTab++;
		push @tlockl, $ttname . "\n" . $tabh{$ttname} . "\n";
	}

	# if any tables require tablelocks, list them
	if (scalar(@tlockl))
	{
		# list of all tablenames and affected functions
		$tablelocks = join("\n", @tlockl) . "\n";
	}

	$tablelocks = "<None>"
		unless (length($tablelocks));
	$hashdups = "<None>"
		unless (length($hashdups));

	$bigstr = doformat($bigstr, 
					   {
						   nfiles => scalar(keys(%{$caqlh->{files}})) ,
						   nqueries => $totquerycnt,
						   nuniq => scalar(keys(%{$caqlh->{queries}})),
						   nbasic => scalar(keys(%{$caqlh->{basic}})),
						   ninsert => $totinsertcnt,
						   hashdups => $hashdups,
						   tablelocks => $tablelocks,
						   numlocktab => $numLockTab,
						   numlockfun => $numLockFunc,
					   }
		);
						   
	return $bigstr;
} # end caql_stats

# enhance bigh with reversed primary key dependency (for caql delete)
sub bigh_fk_fixup
{
	my $bigh = shift;

	while (my ($kk, $vv) = each(%{$bigh}))
	{
		my $tname = $kk;

		next # ignore comments
			if ($tname =~ m/^\_\_/);

		$bigh->{$tname}->{calico} = {}; # special calico attributes

		# XXX XXX NOTE: IUD Exceptions
		# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
		# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
		# XXX XXX: stupid last_operation/shoperation fixup
		if ($tname =~ m/pg\_stat\_last\_(sh)?operation/)
		{
			$bigh->{$tname}->{calico}->{iud_exception} = 1;
			next;
		}
		# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
		# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 

		# track the primary keys for each table
		if (exists($bigh->{$tname}->{indexes}))
		{
			$bigh->{$tname}->{calico}->{pkeys} = [];

			for my $idx (@{$bigh->{$tname}->{indexes}})
			{
				if (exists($idx->{unique}) &&
					$idx->{unique})
				{
					push @{$bigh->{$tname}->{calico}->{pkeys}},
					$idx->{CamelCaseIndexId};
				}
			}
		}

		next
			unless (exists($bigh->{$tname}->{fk_list}));

		# find all unique indexes whose prefixes are oid columns.  If
		# those columns are foreign keys, then this relation is a
		# "dependent class" of the parent relation.  For the
		# purposes of locking, we can lock the parent relation primary
		# key exclusively, which will lock the dependent class.
		#
		# Why this works: if we can lock the primary key of the
		# dependent class, it needs to get a share lock on foreign
		# keys, and one of those foreign keys is a primary key of the
		# parent. 
		my $oidcol2idx = {};

		if (exists($bigh->{$tname}->{indexes}))
		{
			for my $ii (0..(scalar(@{$bigh->{$tname}->{indexes}}) - 1))
			{
				my $idx = $bigh->{$tname}->{indexes}->[$ii];

				# first column (cols[0]) is [name, "type_ops"] pair
				# -- it must be an oid for this to work.
				next
					unless ($idx->{cols}->[0]->[1] eq "oid_ops");
				
				my $colname = $idx->{cols}->[0]->[0];

				$oidcol2idx->{$colname} = []
					unless (exists($oidcol2idx->{$colname}));

				my $idxdesc = {idxname => $idx->{CamelCaseIndexId},
							   firstcol => $colname, idxoffset => $ii };

				# NOTE: if index is not unique, then table is not
				# necessarily a "dependent table", but the parent
				# index still works for locking if it is a primary key

				push @{$oidcol2idx->{$colname}}, $idxdesc;
			}
		} # end for indexes
		
		for my $fk (@{$bigh->{$tname}->{fk_list}})
		{
			my $pktname = $fk->{pktable};

			# track "parent key" dependencies for delete, ie if
			# deleting from this table, then find tables that depend
			# on its primary key
			$bigh->{$pktname}->{pk_dependent} = {}
			unless (exists($bigh->{$pktname}->{pk_dependent}));

			$bigh->{$pktname}->{pk_dependent}->{$tname} = {}
				unless (exists($bigh->{$pktname}->{pk_dependent}->{$tname}));

			my $isvec = "";
			$isvec = " [vector]"
				if ($fk->{type} =~ m/vector/i);
			my $fkentry = "(" . join(", ", @{$fk->{fkcols}}) . 
				") <- (" . join(", ", @{$fk->{pkcols}}) . ")" . 
				$isvec;
			
			# set of primary key cols for tname in pktname
			# listed as "(keycols) <- (fk key cols)"
			$bigh->{$pktname}->{pk_dependent}->{$tname}->{$fkentry} = 1;

			next # does the primary key have a single column?
				unless (1 == scalar(@{$fk->{pkcols}}));

			# get column names of first (and only) column of pk/fk
			my $firstcol = $fk->{fkcols}->[0];
			my $pk1stcol = $fk->{pkcols}->[0]; # should just be oid?
			next 
				unless (exists($oidcol2idx->{$firstcol}));
			
			# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
			# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
			# Great! the first column of the foreign key is the first
			# column of a unique index on this table.  And if it is
			# the first (and only) column of a primary key index on
			# the pktable then note that information.
			# It indicates that this table is a "dependent class" of
			# the parent relation (the "primary key" table), so
			# locking the primary key of the parent table will lock a
			# portion of the primary key on this table. Which is
			# really what you want to do if you want to lock all the
			# records in this relation that depend on the primary key
			# of the parent table.
			# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 
			# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX 

			for my $idxdesc (@{$oidcol2idx->{$firstcol}})
			{
				next
					unless (exists($bigh->{$pktname}->{indexes}));
				
				for my $pkidx (@{$bigh->{$pktname}->{indexes}})
				{
					# must be unique, single col oid index
					next 
						unless (exists($pkidx->{unique}) &&
								$pkidx->{unique} &&
								(1 == scalar(@{$pkidx->{cols}})));

					# first col description: [colname, "type_ops"]
					my $pk1c = $pkidx->{cols}->[0];
						
					next # type must be oid
						unless ($pk1c->[1] eq "oid_ops");

					next # must match name
						unless ($pk1c->[0] eq $pk1stcol);
						
					# finally!!  This unique index on the "primary
					# key" table is the prefix of our primary key 

					my $depidx =
						$bigh->{$tname}->{indexes}->[$idxdesc->{idxoffset}];

					# add "primarykey_prefix" attribute to the
					# dependent table index
					$depidx->{primarykey_prefix} = {
						pktname => $pktname,
						pkrelid => $bigh->{$pktname}->{CamelCaseRelationId},
						pkidx => $pkidx->{CamelCaseIndexId},
						pkcolname => $pk1stcol,
						fkcolname => $firstcol,
					};
						
				} # end for my pkidx
				
			} # end for my idxdesc
				
		} # end for my fk

	} # end while kk vv
} # end bigh_fk_fixup

if (1)
{
	my $whole_file;
	
	{
		my $injson;

		open $injson, "< $glob_glob->{metadata}" 
			or die "cannot open $glob_glob->{metadata}: $!";

        # $$$ $$$ undefine input record separator (\n)
        # and slurp entire file into variable

        local $/;
        undef $/;

		$whole_file = <$injson>;

		close $injson;
	}

	my $bigh = JSON::from_json($whole_file);

	if (exists($glob_glob->{interactive}) && $glob_glob->{interactive} )
	{
		do_interactive($bigh);
		exit(0);
	}

	# enhance bigh with reversed primary key dependency (for caql delete)
	bigh_fk_fixup($bigh);
##	print JSON::to_json($bigh, {pretty => 1, indent => 2, canonical => 1});

	my $caqlh = {queries => {}, files => {}, basic => {} };

	# if infiles is specified, use this file list, else read from command line
	if (exists($glob_glob->{inputfiles}) && $glob_glob->{inputfiles} )
	{
		my $infil;

		open $infil, "< $glob_glob->{inputfiles}" 
			or die "cannot open $glob_glob->{inputfiles}: $!";

		my $filnam;
		while ( $filnam = <$infil> )
		{
			chomp($filnam);
			get_caql($caqlh, $filnam, $bigh);

		} # end for my $filnam

		close $infil;
	}
	else
	{
		for my $filnam (@ARGV)
		{
			get_caql($caqlh, $filnam, $bigh);

		} # end for my $filnam
	}

#	print Data::Dumper->Dump([$caqlh]);

	# deduplicate the iud functions, and build a map (by
	# iud_func_name) to the lists of basic queries sharing the
	# function
	my $iud_dedup = {};
	$glob_glob->{iud2bq} = {};

	my $ii = 1;
	for my $bq (sort(keys(%{$caqlh->{basic}})))
	{
		my $funcname = "caql_basic_fn_$ii";

		$caqlh->{basic}->{$bq}->{func_note} = "";

		$caqlh->{basic}->{$bq}->{func} = 
			dothing($bigh, $bq, $funcname,
					$caqlh->{basic}->{$bq});

		$caqlh->{basic}->{$bq}->{func_number} = $ii;
		$caqlh->{basic}->{$bq}->{func_name} = $funcname;


		# now do the iud functions
		$caqlh->{basic}->{$bq}->{iud_func_name} = 
			"caql_iud_fn_$ii";

		my $iud_func = do_iud($bigh, $bq, 
#							  $caqlh->{basic}->{$bq}->{iud_func_name},
							  # NOTE: don't specify the function name yet
							  "{IUD_FUNC_NAME}",
							  $caqlh->{basic}->{$bq});

		# check if already have this function (but make into single
		# alpha string)
		my $ifq = sql_func_quurl($iud_func);

		if (exists($iud_dedup->{$ifq}))
		{
			# found a duplicate

			$iud_dedup->{$ifq}->{count} += 1;
			$caqlh->{basic}->{$bq}->{iud_func_name} = 
				$iud_dedup->{$ifq}->{name};
			$caqlh->{basic}->{$bq}->{iud_func} = undef;

			# track all basic queries sharing an iud function
			push 
				@{$glob_glob->{iud2bq}->{$iud_dedup->{$ifq}->{name}}},
				$bq;
		}
		else
		{
			# new iud function - add it to the list

			$iud_dedup->{$ifq} = 
			{ count => 1,
			  name => 
				  $caqlh->{basic}->{$bq}->{iud_func_name}};

			# XXX XXX: replace the function name now! we couldn't do
			# it before because it breaks duplicate checking.
			$iud_func = 
				doformat($iud_func,
						 {
							 IUD_FUNC_NAME => 
								 $caqlh->{basic}->{$bq}->{iud_func_name}
						 });

			$caqlh->{basic}->{$bq}->{iud_func} = $iud_func;

			# track all basic queries sharing an iud function
			$glob_glob->{iud2bq}->{$iud_dedup->{$ifq}->{name}} = [ $bq ];
		}

		$ii++;
	}

	if (exists($glob_glob->{gperf}) && $glob_glob->{gperf} )
	{
		# build the gperf input file
		do_gperf($caqlh, $glob_glob->{gperf}, $bigh);

		# run gperf 

		my $gperfstr = 
			"gperf -S 1 --hash-fn-name=cq_hash --lookup-fn-name=cq_lookup" .
			" --duplicates " .
			# XXX XXX: add some more keys to fix dups
			" --key=11,18,20,27,29,39,43,61,92" .
			"  -t $glob_glob->{gperf} ";

		# NOTE: this string is very large -- it's all of catquery.c
		my $gperfout = `$gperfstr`;

		# from perlfunc - get system status
		if ($? != 0)
		{
			my $errstr;
			if ($? == -1) {
				$errstr = "failed to execute: $!\n";
			}
			elsif ($? & 127) {
				$errstr = sprintf "child died with signal %d, %s coredump\n",
				($? & 127),  ($? & 128) ? 'with' : 'without';
			}
			else {
				$errstr = sprintf "child exited with value %d\n", $? >> 8;
			}
			die ("gperf call failed: $errstr");
		}

		# remove inline directives, and change declaration of
		# cq_lookup to static

		$gperfout =~ s/^\_\_inline//gm;
		$gperfout =~ s/^inline//gm;
		# fix newer gperf
		$gperfout =~ s/^\_\_attribute\_\_ \(\(\_\_gnu\_inline\_\_\)\)//gm;
		$gperfout =~ 
			s/^struct caql_hash_cookie \*/static struct caql_hash_cookie */gm;

		# print generated file
		print $gperfout;
	}

	# dump routine
	if (exists($glob_glob->{dump}) && $glob_glob->{dump} )
	{
		if (0)
		{
			for my $bq (sort(keys(%{$caqlh->{basic}})))
			{
				print "$bq  :\n\n";
				
				print Data::Dumper->Dump([$caqlh->{basic}->{$bq}]);
			}
		}
		else
		{
			my %hx;

			# build a json doc keyed by func name, and strip out
			# "extraneous" attributes like the function name, the
			# function body, etc.
			for my $bq (sort(keys(%{$caqlh->{basic}})))
			{
#				print "$bq  :\n\n";
#				print Data::Dumper->Dump([$caqlh->{basic}->{$bq}]);

				my $th = {};
				$th->{basic_query} = $bq;

				for my $bqk (keys(%{$caqlh->{basic}->{$bq}}))
				{
					next if (($bqk =~ m/^func/) && ($bqk !~ /index/));
					$th->{$bqk} = $caqlh->{basic}->{$bq}->{$bqk};
				}
				$hx{$caqlh->{basic}->{$bq}->{func_name}} = $th;
			}
			print  JSON::to_json(\%hx, 
								 {pretty => 1, indent => 2, 
								  canonical => 1});

		}
	}

	# caql definitions
	if (exists($glob_glob->{uniqdef}) && length($glob_glob->{uniqdef}))
	{
		my $f2mfh;
		open $f2mfh, "> $glob_glob->{uniqdef}" or 
			die "cannot open $glob_glob->{uniqdef}: $!";

		print $f2mfh JSON::to_json($caqlh->{queries}, 
								  {pretty => 1, indent => 2, 
								   canonical => 1});
		close $f2mfh;
	}
	# caql definitions
	if (exists($glob_glob->{basedef}) && length($glob_glob->{basedef}))
	{
		my $f2mfh;
		open $f2mfh, "> $glob_glob->{basedef}" or 
			die "cannot open $glob_glob->{basedef}: $!";

		print $f2mfh JSON::to_json($caqlh->{basic}, 
								  {pretty => 1, indent => 2, 
								   canonical => 1});
		close $f2mfh;
	}

	# build filemap for pablopcatso
	if (exists($glob_glob->{fil2tab}) &&
		exists($glob_glob->{filemap}) && length($glob_glob->{filemap}))
	{

		my %hx;

		for my $kk (keys(%{$glob_glob->{fil2tab}}))
		{
			$hx{$kk} = [];
			push @{$hx{$kk}}, (sort(keys(%{$glob_glob->{fil2tab}->{$kk}})));
		}

		my $f2mfh;
		open $f2mfh, "> $glob_glob->{filemap}" or 
			die "cannot open $glob_glob->{filemap}: $!";

		print $f2mfh JSON::to_json(\%hx, 
								  {pretty => 1, indent => 2, 
								   canonical => 1});
		close $f2mfh;
	}


}

sub caql_logquery
{
	my $bigstr = <<'EOF_bigstr';

void
caql_logquery(const char *funcname, const char *filename, int lineno,
			  int uniqquery_code, Oid arg1)
{
EOF_bigstr

	if ($glob_glob->{logquery})
	{

		$bigstr .= <<'EOF_bigstr';
	SUPPRESS_ERRCONTEXT_DECLARE;
	SUPPRESS_ERRCONTEXT_PUSH();
	elog(LOG, "catquery: %s caller: %s %d %d %d ",
		funcname, filename, lineno, uniqquery_code, arg1);
	SUPPRESS_ERRCONTEXT_POP();

EOF_bigstr
	}
	elsif ($glob_glob->{logquery_hash})
	{
		$bigstr .= <<'EOF_bigstr';
	CaQLLogTag tag;
	CaQLLogEntry *entry;
	int			hashcode;
	int			len;
	bool		found = false;
	SUPPRESS_ERRCONTEXT_DECLARE;

	if (!gp_enable_caql_logging)
		return;

	if (CaQLLogHash == NULL)
	{
		HASHCTL hash_ctl;
		hash_ctl.keysize = sizeof(CaQLLogTag);
		hash_ctl.entrysize = sizeof(CaQLLogTag);
		hash_ctl.hash = tag_hash;

		CaQLLogHash = hash_create("caql log hash",
						1000,
						&hash_ctl,
						HASH_ELEM | HASH_FUNCTION);
	}

	/* memset is required, as we use memcmp on this */
	MemSet(&tag, 0, sizeof(CaQLLogTag));
	len = strlen(filename);
	if (len > MAXPGPATH - 1)
		len = MAXPGPATH - 1;
	memcpy(tag.filename, filename, len);
	tag.lineno = lineno;

	/* compute the hash */
	hashcode = caqlLogHashCode(&tag);

	/* look up the hash table to see if this line has been logged before */
	entry = (CaQLLogEntry *) hash_search_with_hash_value(CaQLLogHash,
														 (void *) &tag,
														 hashcode,
														 HASH_ENTER, &found);
	SUPPRESS_ERRCONTEXT_PUSH();
	if (!found)
		elog(LOG, "catquery: %s caller: %s %d %d %d ",
			funcname, filename, lineno, uniqquery_code, arg1);
	SUPPRESS_ERRCONTEXT_POP();
EOF_bigstr
	}

	$bigstr .= <<'EOF_bigstr';
}

EOF_bigstr
	return $bigstr;
}
